diff --git a/README.md b/README.md index 49f616964..3da301dba 100644 --- a/README.md +++ b/README.md @@ -217,8 +217,9 @@ and which gives information about the app for the Launcher. { "id": "appid", // 7 character app id "name": "Readable name", // readable name "shortName": "Short name", // short name for launcher - "icon": "icon.png", // icon in apps/ + "version": "0v01", // the version of this app "description": "...", // long description (can contain markdown) + "icon": "icon.png", // icon in apps/ "type":"...", // optional(if app) - // 'app' - an application // 'widget' - a widget @@ -226,6 +227,7 @@ and which gives information about the app for the Launcher. // 'bootloader' - code that runs at startup only // 'RAM' - code that runs and doesn't upload anything to storage "tags": "", // comma separated tag list for searching + "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' "readme": "README.md", // if supplied, a link to a markdown-style text file diff --git a/apps.json b/apps.json index df4a649f3..51cca784b 100644 --- a/apps.json +++ b/apps.json @@ -1,24 +1,61 @@ [ - { "id": "boot", + { + "id": "fwupdate", + "name": "Firmware Update", + "version": "0.01", + "description": "Uploads new Espruino firmwares to Bangle.js 2", + "icon": "app.png", + "type": "RAM", + "tags": "tools,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, + "storage": [ + + ], + "sortorder": -20 + }, + { + "id": "boot", "name": "Bootloader", - "tags": "tool,system,b2", - "type":"bootloader", - "icon": "bootloader.png", - "version":"0.31", + "version": "0.32", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", + "icon": "bootloader.png", + "type": "bootloader", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":".boot0","url":"boot0.js"}, {"name":".bootcde","url":"bootloader.js"}, {"name":"bootupdate.js","url":"bootupdate.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "health", - "name": "Health Tracking", - "tags": "tool,system,b2", + { + "id": "messages", + "name": "Messages", + "version": "0.01", + "description": "App to display notifications from iOS and Gadgetbridge (BETA)", "icon": "app.png", - "version":"0.01", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"messages.app.js","url":"app.js"}, + {"name":"messages.img","url":"app-icon.js","evaluate":true}, + {"name":"messages.boot.js","url":"boot.js"}, + {"name":"messages.wid.js","url":"widget.js"} + ], + "sortorder": -9 + }, + { + "id": "health", + "name": "Health Tracking", + "version": "0.01", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"health.app.js","url":"app.js"}, @@ -26,200 +63,221 @@ {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} ], - "sortorder" : -10 + "sortorder": -8 }, - { "id": "moonphase", + + { + "id": "moonphase", "name": "Moonphase", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Shows current moon phase. Now with GPS function.", + "icon": "app.png", "tags": "", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"moonphase.app.js","url":"app.js"}, {"name":"moonphase.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "daysl", + { + "id": "daysl", "name": "Days left", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", + "icon": "app.png", "tags": "", - "allow_emulator":false, + "supports": ["BANGLEJS"], + "allow_emulator": false, "storage": [ - {"name":"daysl.app.js","url":"app.js"}, - {"name":"daysl.img","url":"app-icon.js","evaluate":true}, - {"name":"daysl.wid.js","url":"widget.js"} + {"name":"daysl.app.js","url":"app.js"}, + {"name":"daysl.img","url":"app-icon.js","evaluate":true}, + {"name":"daysl.wid.js","url":"widget.js"} ] }, - { "id": "launch", - "name": "Launcher (Default)", - "shortName":"Launcher", + { + "id": "launch", + "name": "Launcher (Bangle.js 1 default)", + "shortName": "Launcher", + "version": "0.07", + "description": "This is needed by Bangle.js 1.0 to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "icon": "app.png", - "version":"0.07", - "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", - "tags": "tool,system,launcher,b2", - "type":"launch", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"launch.app.js","url":"app.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "launchb2", - "name": "Launcher (Bangle.js 2)", - "shortName":"Launcher", + { + "id": "launchb2", + "name": "Launcher (Bangle.js 2 default)", + "shortName": "Launcher", + "version": "0.03", + "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", "icon": "app.png", - "version":"0.03", - "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.", - "tags": "tool,system,launcher,b2,bno1", - "type":"launch", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS2"], "storage": [ {"name":"launchb2.app.js","url":"app.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "about", + { + "id": "about", "name": "About", - "icon": "app.png", - "version":"0.09", + "version": "0.09", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", - "tags": "tool,system,b2", - "allow_emulator":true, + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"about.app.js","url":"app.js"}, {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "locale", + { + "id": "locale", "name": "Languages", - "icon": "locale.png", - "version":"0.09", + "version": "0.09", "description": "Translations for different countries", - "tags": "tool,system,locale,translate,b2", + "icon": "locale.png", "type": "locale", - "custom":"locale.html", + "tags": "tool,system,locale,translate", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "custom": "locale.html", "storage": [ {"name":"locale"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "notify", + { + "id": "notify", "name": "Notifications (default)", - "shortName":"Notifications", - "icon": "notify.png", - "version":"0.11", + "shortName": "Notifications", + "version": "0.11", "description": "Provides the default `notify` module used by applications to display notifications in a bar at the top of the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", - "tags": "widget", + "icon": "notify.png", "type": "notify", + "tags": "widget", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"notify","url":"notify.js"} ] }, - { "id": "notifyfs", + { + "id": "notifyfs", "name": "Fullscreen Notifications", - "shortName":"Notifications", - "icon": "notify.png", - "version":"0.12", + "shortName": "Notifications", + "version": "0.12", "description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.", - "tags": "widget,b2", + "icon": "notify.png", "type": "notify", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"notify","url":"notify.js"} ] }, - { "id": "welcome", + { + "id": "welcome", "name": "Welcome", - "icon": "app.png", - "version":"0.12", + "version": "0.12", "description": "Appears at first boot and explains how to use Bangle.js", + "icon": "app.png", "tags": "start,welcome", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"welcome.boot.js","url":"boot.js"}, {"name":"welcome.app.js","url":"app.js"}, {"name":"welcome.settings.js","url":"settings.js"}, {"name":"welcome.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"name":"welcome.json"} - ] + "data": [{"name":"welcome.json"}] }, - { "id": "mywelcome", + { + "id": "mywelcome", "name": "Customised Welcome", "shortName": "My Welcome", - "icon": "app.png", - "version":"0.12", + "version": "0.12", "description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting", + "icon": "app.png", "tags": "start,welcome", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "custom": "custom.html", "storage": [ {"name":"mywelcome.boot.js","url":"boot.js"}, {"name":"mywelcome.app.js","url":"app.js"}, {"name":"mywelcome.settings.js","url":"settings.js"}, {"name":"mywelcome.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"name":"mywelcome.json"} - ] + "data": [{"name":"mywelcome.json"}] }, - { "id": "gbridge", + { + "id": "gbridge", "name": "Gadgetbridge", - "icon": "app.png", - "version":"0.24", + "version": "0.24", "description": "The default notification handler for Gadgetbridge notifications from Android", - "tags": "tool,system,android,widget,b2", + "icon": "app.png", + "type": "widget", + "tags": "tool,system,android,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies": {"notify":"type"}, "readme": "README.md", - "type":"widget", - "dependencies": { "notify":"type" }, "storage": [ {"name":"gbridge.settings.js","url":"settings.js"}, {"name":"gbridge.img","url":"app-icon.js","evaluate":true}, {"name":"gbridge.wid.js","url":"widget.js"} ], - "data": [ - {"name":"gbridge.json"} - ] + "data": [{"name":"gbridge.json"}] }, - { "id": "mclock", + { + "id": "mclock", "name": "Morphing Clock", - "icon": "clock-morphing.png", - "version":"0.07", + "version": "0.07", "description": "7 segment clock that morphs between minutes and hours", + "icon": "clock-morphing.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"mclock.app.js","url":"clock-morphing.js"}, {"name":"mclock.img","url":"clock-morphing-icon.js","evaluate":true} ], - "sortorder" : -9 + "sortorder": -9 }, - { "id": "setting", + { + "id": "setting", "name": "Settings", - "icon": "settings.png", - "version":"0.30", + "version": "0.30", "description": "A menu for setting up Bangle.js", - "tags": "tool,system,b2", + "icon": "settings.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"setting.app.js","url":"settings.js"}, {"name":"setting.img","url":"settings-icon.js","evaluate":true} ], - "data": [ - {"name":"setting.json", "url":"settings.min.json","evaluate":true} - ], - "sortorder" : -2 + "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], + "sortorder": -2 }, - { "id": "alarm", + { + "id": "alarm", "name": "Default Alarm & Timer", - "shortName":"Alarms", - "icon": "app.png", - "version":"0.13", + "shortName": "Alarms", + "version": "0.13", "description": "Set and respond to alarms and timers", - "tags": "tool,alarm,widget,b2", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"alarm.app.js","url":"app.js"}, {"name":"alarm.boot.js","url":"boot.js"}, @@ -227,33 +285,35 @@ {"name":"alarm.img","url":"app-icon.js","evaluate":true}, {"name":"alarm.wid.js","url":"widget.js"} ], - "data": [ - {"name":"alarm.json"} - ] + "data": [{"name":"alarm.json"}] }, - { "id": "wclock", + { + "id": "wclock", "name": "Word Clock", - "icon": "clock-word.png", - "version":"0.03", + "version": "0.03", "description": "Display Time as Text", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-word.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"wclock.app.js","url":"clock-word.js"}, {"name":"wclock.img","url":"clock-word-icon.js","evaluate":true} ] }, - { "id": "fontclock", + { + "id": "fontclock", "name": "Font Clock", - "icon": "fontclock.png", - "version":"0.01", + "version": "0.01", "description": "Choose the font and design of clock face from a library of available designs", + "icon": "fontclock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":false, - "readme": "README.md", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, "storage": [ {"name":"fontclock.app.js","url":"fontclock.js"}, {"name":"fontclock.img","url":"fontclock-icon.js","evaluate":true}, @@ -270,16 +330,18 @@ {"name":"fontclock.font.vector50.js","url":"fontclock.font.vector50.js"} ] }, - { "id": "slidingtext", + { + "id": "slidingtext", "name": "Sliding Clock", - "icon": "slidingtext.png", - "version":"0.07", + "version": "0.07", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported", + "icon": "slidingtext.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":false, - "readme": "README.md", - "custom":"custom.html", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, "storage": [ {"name":"slidingtext.app.js","url":"slidingtext.js"}, {"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true}, @@ -293,16 +355,18 @@ {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"} ] }, - { "id": "solarclock", + { + "id": "solarclock", "name": "Solar Clock", - "icon": "solar_clock.png", - "version":"0.02", + "version": "0.02", "description": "Using your current or chosen location the solar watch face shows the Sun's sky position, time and date. Also allows you to wind backwards and forwards in time to see the sun's position", + "icon": "solar_clock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":false, - "readme": "README.md", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, "storage": [ {"name":"solarclock.app.js","url":"solar_clock.js"}, {"name":"solarclock.img","url":"solar_clock-icon.js","evaluate":true}, @@ -320,42 +384,48 @@ {"name":"solar_loc.Seoul.json","url":"solar_loc.Seoul.json"} ] }, - { "id": "sweepclock", + { + "id": "sweepclock", "name": "Sweep Clock", - "icon": "sweepclock.png", - "version":"0.04", + "version": "0.04", "description": "Smooth sweep secondhand with single hour numeral. Use button 1 to toggle the numeral font, button 3 to change the colour theme and button 4 to change the date placement", + "icon": "sweepclock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"sweepclock.app.js","url":"sweepclock.js"}, {"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true} ] }, - { "id": "matrixclock", + { + "id": "matrixclock", "name": "Matrix Clock", - "icon": "matrixclock.png", - "version":"0.02", + "version": "0.02", "description": "inspired by The Matrix, a clock of the same style", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", + "icon": "matrixclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"matrixclock.app.js","url":"matrixclock.js"}, {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true} ] }, - { "id": "imgclock", + { + "id": "imgclock", "name": "Image background clock", - "shortName":"Image Clock", - "icon": "app.png", - "version":"0.08", + "shortName": "Image Clock", + "version": "0.08", "description": "A clock with an image as a background", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type" : "clock", + "supports": ["BANGLEJS"], "custom": "custom.html", "storage": [ {"name":"imgclock.app.js","url":"app.js"}, @@ -365,245 +435,262 @@ {"name":"imgclock.face.bg","content":""} ] }, - { "id": "impwclock", + { + "id": "impwclock", "name": "Imprecise Word Clock", - "icon": "clock-impword.png", - "version":"0.03", + "version": "0.03", "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", + "icon": "clock-impword.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"impwclock.app.js","url":"clock-impword.js"}, {"name":"impwclock.img","url":"clock-impword-icon.js","evaluate":true} ] }, - { "id": "aclock", + { + "id": "aclock", "name": "Analog Clock", - "icon": "clock-analog.png", "version": "0.15", "description": "An Analog Clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-analog.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, {"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true} ] }, - { "id": "clock2x3", + { + "id": "clock2x3", "name": "2x3 Pixel Clock", - "icon": "clock2x3.png", - "version":"0.05", + "version": "0.05", "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", - "tags": "clock,b2", + "icon": "clock2x3.png", "type": "clock", - "allow_emulator":true, + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, {"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true} ] }, - { "id": "geissclk", + { + "id": "geissclk", "name": "Geiss Clock", - "icon": "clock.png", - "version":"0.03", + "version": "0.03", "description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation", - "tags": "clock,bno2", - "type":"clock", + "icon": "clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"geissclk.app.js","url":"clock.js"}, {"name":"geissclk.precompute.js","url":"precompute.js"}, {"name":"geissclk.img","url":"clock-icon.js","evaluate":true} ], - "data": [ - {"name":"geissclk.0.map"}, - {"name":"geissclk.1.map"}, - {"name":"geissclk.2.map"}, - {"name":"geissclk.3.map"}, - {"name":"geissclk.4.map"}, - {"name":"geissclk.5.map"}, - {"name":"geissclk.0.pal"}, - {"name":"geissclk.1.pal"}, - {"name":"geissclk.2.pal"} - ] + "data": [{"name":"geissclk.0.map"},{"name":"geissclk.1.map"},{"name":"geissclk.2.map"},{"name":"geissclk.3.map"},{"name":"geissclk.4.map"},{"name":"geissclk.5.map"},{"name":"geissclk.0.pal"},{"name":"geissclk.1.pal"},{"name":"geissclk.2.pal"}] }, - { "id": "trex", + { + "id": "trex", "name": "T-Rex", - "icon": "trex.png", - "version":"0.04", + "version": "0.04", "description": "T-Rex game in the style of Chrome's offline game", - "tags": "game,b2", - "allow_emulator":true, + "icon": "trex.png", + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"trex.app.js","url":"trex.js"}, {"name":"trex.img","url":"trex-icon.js","evaluate":true}, {"name":"trex.settings.js","url":"settings.js"} ], - "data": [ - {"name":"trex.score", "storageFile": true} - ] + "data": [{"name":"trex.score","storageFile":true}] }, - { "id": "astroid", + { + "id": "astroid", "name": "Asteroids!", - "icon": "asteroids.png", - "version":"0.03", + "version": "0.03", "description": "Retro asteroids game", - "tags": "game,b2", - "allow_emulator":true, + "icon": "asteroids.png", + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"astroid.app.js","url":"asteroids.js"}, {"name":"astroid.img","url":"asteroids-icon.js","evaluate":true} ] }, - { "id": "clickms", + { + "id": "clickms", "name": "Click Master", - "icon": "click-master.png", - "version":"0.01", + "version": "0.01", "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", + "icon": "click-master.png", "tags": "game", + "supports": ["BANGLEJS"], "storage": [ {"name":"clickms.app.js","url":"click-master.js"}, {"name":"clickms.img","url":"click-master-icon.js","evaluate":true} ] }, - { "id": "horsey", + { + "id": "horsey", "name": "Horse Race!", - "icon": "horse-race.png", - "version":"0.01", + "version": "0.01", "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", + "icon": "horse-race.png", "tags": "game", + "supports": ["BANGLEJS"], "storage": [ {"name":"horsey.app.js","url":"horse-race.js"}, {"name":"horsey.img","url":"horse-race-icon.js","evaluate":true} ] }, - { "id": "compass", + { + "id": "compass", "name": "Compass", - "icon": "compass.png", - "version":"0.03", + "version": "0.04", "description": "Simple compass that points North", + "icon": "compass.png", "tags": "tool,outdoors", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"compass.app.js","url":"compass.js"}, {"name":"compass.img","url":"compass-icon.js","evaluate":true} ] }, - { "id": "gpstime", + { + "id": "gpstime", "name": "GPS Time", - "icon": "gpstime.png", - "version":"0.04", + "version": "0.05", "description": "Update the Bangle.js's clock based on the time from the GPS receiver", + "icon": "gpstime.png", "tags": "tool,gps", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"gpstime.app.js","url":"gpstime.js"}, {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true} ] }, - { "id": "openloc", + { + "id": "openloc", "name": "Open Location / Plus Codes", "shortName": "Open Location", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Convert your current GPS location to a series of characters", + "icon": "app.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"openloc.app.js","url":"app.js"}, {"name":"openloc.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "speedo", + { + "id": "speedo", "name": "Speedo", - "icon": "speedo.png", - "version":"0.05", + "version": "0.05", "description": "Show the current speed according to the GPS", - "tags": "tool,outdoors,gps,b2", + "icon": "speedo.png", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"speedo.app.js","url":"speedo.js"}, {"name":"speedo.img","url":"speedo-icon.js","evaluate":true} ] }, - { "id": "gpsrec", + { + "id": "gpsrec", "name": "GPS Recorder", - "icon": "app.png", - "version":"0.24", - "interface": "interface.html", + "version": "0.24", "description": "Application that allows you to record a GPS track. Can run in background", + "icon": "app.png", "tags": "tool,outdoors,gps,widget", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"gpsrec.app.js","url":"app.js"}, {"name":"gpsrec.img","url":"app-icon.js","evaluate":true}, {"name":"gpsrec.wid.js","url":"widget.js"}, {"name":"gpsrec.settings.js","url":"settings.js"} ], - "data": [ - {"name":"gpsrec.json"}, - {"wildcard":".gpsrc?","storageFile": true} - ] + "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}] }, - { "id": "gpsnav", + { + "id": "gpsnav", "name": "GPS Navigation", - "icon": "icon.png", - "version":"0.05", + "version": "0.05", "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor", + "icon": "icon.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "readme": "README.md", - "interface":"waypoints.html", + "interface": "waypoints.html", "storage": [ {"name":"gpsnav.app.js","url":"app.min.js"}, {"name":"gpsnav.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"name":"waypoints.json","url":"waypoints.json"} - ] + "data": [{"name":"waypoints.json","url":"waypoints.json"}] }, - { "id": "heart", + { + "id": "heart", "name": "Heart Rate Recorder", - "icon": "app.png", - "version":"0.06", - "interface": "interface.html", + "shortName": "HRM Record", + "version": "0.07", "description": "Application that allows you to record your heart rate. Can run in background", + "icon": "app.png", "tags": "tool,health,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "interface": "interface.html", "storage": [ {"name":"heart.app.js","url":"app.js"}, {"name":"heart.img","url":"app-icon.js","evaluate":true}, {"name":"heart.wid.js","url":"widget.js"} ], - "data": [ - {"name":"heart.json"}, - {"wildcard":".heart?","storageFile": true} - ] + "data": [{"name":"heart.json"},{"wildcard":".heart?","storageFile":true}] }, - { "id": "slevel", + { + "id": "slevel", "name": "Spirit Level", - "icon": "spiritlevel.png", - "version":"0.01", + "version": "0.01", "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat", + "icon": "spiritlevel.png", "tags": "tool", + "supports": ["BANGLEJS"], "storage": [ {"name":"slevel.app.js","url":"spiritlevel.js"}, {"name":"slevel.img","url":"spiritlevel-icon.js","evaluate":true} ] }, - { "id": "files", + { + "id": "files", "name": "App Manager", - "icon": "files.png", - "version":"0.07", + "version": "0.07", "description": "Show currently installed apps, free space, and allow their deletion from the watch", + "icon": "files.png", "tags": "tool,system,files", + "supports": ["BANGLEJS"], "storage": [ {"name":"files.app.js","url":"files.js"}, {"name":"files.img","url":"files-icon.js","evaluate":true} ] }, - { "id": "weather", + { + "id": "weather", "name": "Weather", - "icon": "icon.png", - "version":"0.10", + "version": "0.10", "description": "Show Gadgetbridge weather report", - "readme": "readme.md", + "icon": "icon.png", "tags": "widget,outdoors", + "supports": ["BANGLEJS"], + "readme": "readme.md", "storage": [ {"name":"weather.app.js","url":"app.js"}, {"name":"weather.wid.js","url":"widget.js"}, @@ -611,30 +698,32 @@ {"name":"weather.img","url":"icon.js","evaluate":true}, {"name":"weather.settings.js","url":"settings.js"} ], - "data": [ - {"name": "weather.json"} - ] + "data": [{"name":"weather.json"}] }, - { "id": "chargeanim", + { + "id": "chargeanim", "name": "Charge Animation", - "icon": "icon.png", - "version":"0.01", + "version": "0.01", "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", + "icon": "icon.png", "tags": "battery", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"chargeanim.app.js","url":"app.js"}, {"name":"chargeanim.boot.js","url":"boot.js"}, {"name":"chargeanim.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "bluetoothdock", + { + "id": "bluetoothdock", "name": "Bluetooth Dock", - "shortName":"Dock", - "icon": "app.png", - "version":"0.01", + "shortName": "Dock", + "version": "0.01", "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen", + "icon": "app.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"bluetoothdock.app.js","url":"app.js"}, @@ -642,190 +731,242 @@ {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widbat", + { + "id": "widbat", "name": "Battery Level Widget", - "icon": "widget.png", - "version":"0.08", + "version": "0.09", "description": "Show the current battery level and charging status in the top right of the clock", - "tags": "widget,battery,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbat.wid.js","url":"widget.js"} ] }, - { "id": "widlock", - "name": "Lock Widget", + { + "id": "widbatv", + "name": "Battery Level Widget (Vertical)", + "version": "0.01", + "description": "Slim, vertical battery widget that only takes up 14px", "icon": "widget.png", - "version":"0.03", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widbatv.wid.js","url":"widget.js"} + ] + }, + { + "id": "widlock", + "name": "Lock Widget", + "version": "0.03", "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked", - "tags": "widget,lock,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,lock", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widlock.wid.js","url":"widget.js"} ] }, - { "id": "widbatpc", + { + "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "icon": "widget.png", - "version":"0.13", + "version": "0.13", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", - "tags": "widget,battery,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widbatpc.wid.js","url":"widget.js"}, {"name":"widbatpc.settings.js","url":"settings.js"} ], - "data": [ - {"name":"widbatpc.json"} - ] + "data": [{"name":"widbatpc.json"}] }, - { "id": "widbatwarn", + { + "id": "widbatwarn", "name": "Battery Warning", "shortName": "Battery Warning", - "icon": "widget.png", - "readme": "README.md", - "version":"0.02", + "version": "0.02", "description": "Show a warning when the battery runs low.", + "icon": "widget.png", + "type": "widget", "tags": "tool,battery", - "type":"widget", - "dependencies": { "notify":"type" }, + "supports": ["BANGLEJS"], + "dependencies": {"notify":"type"}, + "readme": "README.md", "storage": [ {"name":"widbatwarn.wid.js","url":"widget.js"}, {"name":"widbatwarn.settings.js","url":"settings.js"} ], - "data": [ - {"name":"widbatwarn.json"} - ] + "data": [{"name":"widbatwarn.json"}] }, - { "id": "widbt", + { + "id": "widbt", "name": "Bluetooth Widget", - "icon": "widget.png", - "version":"0.06", + "version": "0.07", "description": "Show the current Bluetooth connection status in the top right of the clock", - "tags": "widget,bluetooth,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbt.wid.js","url":"widget.js"} ] }, - { "id": "widchime", + { + "id": "widchime", "name": "Hour Chime", - "icon": "widget.png", - "version":"0.02", + "version": "0.02", "description": "Buzz or beep on every whole hour.", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widchime.wid.js","url":"widget.js"}, {"name":"widchime.settings.js","url":"settings.js"} ], - "data": [ - {"name":"widchime.json"} - ] + "data": [{"name":"widchime.json"}] }, - { "id": "widram", + { + "id": "widram", "name": "RAM Widget", - "shortName":"RAM Widget", - "icon": "widget.png", - "version":"0.01", + "shortName": "RAM Widget", + "version": "0.01", "description": "Display your Bangle's available RAM percentage in a widget", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widram.wid.js","url":"widget.js"} ] }, - { "id": "hrm", + { + "id": "hrm", "name": "Heart Rate Monitor", - "icon": "heartrate.png", - "version":"0.05", + "version": "0.06", "description": "Measure your heart rate and see live sensor data", + "icon": "heartrate.png", "tags": "health", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"hrm.app.js","url":"heartrate.js"}, {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true} ] }, - { "id": "widhrm", + { + "id": "widhrm", "name": "Simple Heart Rate widget", - "icon": "widget.png", - "version":"0.04", + "version": "0.05", "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", - "tags": "health,widget", + "icon": "widget.png", "type": "widget", + "tags": "health,widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widhrm.wid.js","url":"widget.js"} ] }, - { "id": "stetho", - "name": "Stethoscope", - "icon": "stetho.png", + { "id": "bthrm", + "name": "Bluetooth Heart Rate Monitor", + "shortName":"BT HRM", "version":"0.01", + "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", + "icon": "app.png", + "tags": "health,bluetooth", + "type": "boot", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthrm.boot.js","url":"boot.js"}, + {"name":"bthrm.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "stetho", + "name": "Stethoscope", + "version": "0.01", "description": "Hear your heart rate", + "icon": "stetho.png", "tags": "health", + "supports": ["BANGLEJS"], "storage": [ {"name":"stetho.app.js","url":"stetho.js"}, {"name":"stetho.img","url":"stetho-icon.js","evaluate":true} ] }, - { "id": "swatch", + { + "id": "swatch", "name": "Stopwatch", - "icon": "stopwatch.png", - "version":"0.07", - "interface": "interface.html", + "version": "0.07", "description": "Simple stopwatch with Lap Time logging to a JSON file", + "icon": "stopwatch.png", "tags": "health", - "allow_emulator":true, + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", + "allow_emulator": true, "storage": [ {"name":"swatch.app.js","url":"stopwatch.js"}, {"name":"swatch.img","url":"stopwatch-icon.js","evaluate":true} ] }, - { "id": "hidmsic", + { + "id": "hidmsic", "name": "Bluetooth Music Controls", "shortName": "Music Control", - "icon": "hid-music.png", - "version":"0.02", + "version": "0.02", "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!", + "icon": "hid-music.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidmsic.app.js","url":"hid-music.js"}, {"name":"hidmsic.img","url":"hid-music-icon.js","evaluate":true} ] }, - { "id": "hidkbd", + { + "id": "hidkbd", "name": "Bluetooth Keyboard", "shortName": "Bluetooth Kbd", - "icon": "hid-keyboard.png", - "version":"0.02", + "version": "0.02", "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps", + "icon": "hid-keyboard.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidkbd.app.js","url":"hid-keyboard.js"}, {"name":"hidkbd.img","url":"hid-keyboard-icon.js","evaluate":true} ] }, - { "id": "hidbkbd", + { + "id": "hidbkbd", "name": "Binary Bluetooth Keyboard", "shortName": "Binary BT Kbd", - "icon": "hid-binary-keyboard.png", - "version":"0.02", + "version": "0.02", "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want", + "icon": "hid-binary-keyboard.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidbkbd.app.js","url":"hid-binary-keyboard.js"}, {"name":"hidbkbd.img","url":"hid-binary-keyboard-icon.js","evaluate":true} ] }, - { "id": "animals", + { + "id": "animals", "name": "Animals Game", - "icon": "animals.png", - "version":"0.01", + "version": "0.01", "description": "Simple toddler's game - displays a different number of animals each time the screen is pressed", + "icon": "animals.png", "tags": "game", + "supports": ["BANGLEJS"], "storage": [ {"name":"animals.app.js","url":"animals.js"}, {"name":"animals.img","url":"animals-icon.js","evaluate":true}, @@ -839,36 +980,43 @@ {"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true} ] }, - { "id": "qrcode", + { + "id": "qrcode", "name": "Custom QR Code", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Use this to upload a customised QR code to Bangle.js", - "tags": "qrcode,b2", - "custom": "custom.html", "customConnect":true, + "icon": "app.png", + "tags": "qrcode", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, "storage": [ {"name":"qrcode.app.js"}, {"name":"qrcode.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "beer", + { + "id": "beer", "name": "Beer Compass", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "custom": "custom.html", "storage": [ {"name":"beer.app.js"}, {"name":"beer.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "route", + { + "id": "route", "name": "Route Viewer", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Upload a KML file of a route, and have your watch display a map with how far around it you are", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "custom": "custom.html", "storage": [ {"name":"route.app.js"}, @@ -878,10 +1026,11 @@ { "id": "ncstart", "name": "NCEU Startup", - "icon": "start.png", - "version":"0.06", + "version": "0.06", "description": "NodeConfEU 2019 'First Start' Sequence", + "icon": "start.png", "tags": "start,welcome", + "supports": ["BANGLEJS"], "storage": [ {"name":"ncstart.app.js","url":"start.js"}, {"name":"ncstart.boot.js","url":"boot.js"}, @@ -893,104 +1042,118 @@ {"name":"nc-nodew.img","url":"start-nodew.js","evaluate":true}, {"name":"nc-tf.img","url":"start-tf.js","evaluate":true} ], - "data": [ - {"name":"ncstart.json"} - ] + "data": [{"name":"ncstart.json"}] }, - { "id": "ncfrun", + { + "id": "ncfrun", "name": "NCEU 5K Fun Run", - "icon": "nceu-funrun.png", - "version":"0.01", + "version": "0.01", "description": "Display a map of the NodeConf EU 2019 5K Fun Run route and your location on it", + "icon": "nceu-funrun.png", "tags": "health", + "supports": ["BANGLEJS"], "storage": [ {"name":"ncfrun.app.js","url":"nceu-funrun.js"}, {"name":"ncfrun.img","url":"nceu-funrun-icon.js","evaluate":true} ] }, - { "id": "widnceu", + { + "id": "widnceu", "name": "NCEU Logo Widget", - "icon": "widget.png", - "version":"0.02", + "version": "0.02", "description": "Show the NodeConf EU logo in the top left", + "icon": "widget.png", + "type": "widget", "tags": "widget", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widnceu.wid.js","url":"widget.js"} ] }, - { "id": "sclock", + { + "id": "sclock", "name": "Simple Clock", - "icon": "clock-simple.png", - "version":"0.06", + "version": "0.07", "description": "A Simple Digital Clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-simple.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"sclock.app.js","url":"clock-simple.js"}, {"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true} ] }, - { "id": "s7clk", + { + "id": "s7clk", "name": "Simple 7 segment Clock", - "icon": "icon.png", - "version":"0.03", + "version": "0.03", "description": "A simple 7 segment Clock with date", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"s7clk.app.js","url":"app.js"}, {"name":"s7clk.img","url":"icon.js","evaluate":true} ] }, - { "id": "vibrclock", + { + "id": "vibrclock", "name": "Vibrate Clock", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"vibrclock.app.js","url":"app.js"}, {"name":"vibrclock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "svclock", + { + "id": "svclock", "name": "Simple V-Clock", - "icon": "vclock-simple.png", - "version":"0.03", + "version": "0.04", "description": "Modification of Simple Clock 0.04 to use Vectorfont", + "icon": "vclock-simple.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"svclock.app.js","url":"vclock-simple.js"}, {"name":"svclock.img","url":"vclock-simple-icon.js","evaluate":true} ] }, - { "id": "dclock", + { + "id": "dclock", "name": "Dev Clock", - "icon": "clock-dev.png", - "version":"0.10", + "version": "0.10", "description": "A Digital Clock including timestamp (tst), beats(@), days in current month (dm) and days since new moon (l)", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-dev.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"dclock.app.js","url":"clock-dev.js"}, {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true} ] }, - { "id": "gesture", + { + "id": "gesture", "name": "Gesture Test", - "icon": "gesture.png", - "version":"0.01", + "version": "0.01", "description": "BETA! Uploads a basic Tensorflow Gesture model, and then outputs each gesture as a message", + "icon": "gesture.png", + "type": "app", "tags": "gesture,ai", - "type":"app", + "supports": ["BANGLEJS"], "storage": [ {"name":"gesture.app.js","url":"gesture.js"}, {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true}, @@ -998,39 +1161,45 @@ {"name":"gesture.img","url":"gesture-icon.js","evaluate":true} ] }, - { "id": "pparrot", + { + "id": "pparrot", "name": "Party Parrot", - "icon": "party-parrot.png", - "version":"0.01", + "version": "0.01", "description": "Party with a parrot on your wrist", + "icon": "party-parrot.png", + "type": "app", "tags": "party,parrot,lol", - "type":"app", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"pparrot.app.js","url":"party-parrot.js"}, {"name":"pparrot.img","url":"party-parrot-icon.js","evaluate":true} ] }, - { "id": "hrings", + { + "id": "hrings", "name": "Hypno Rings", - "icon": "hypno-rings.png", - "version":"0.01", + "version": "0.01", "description": "Experiment with trippy rings, press buttons for change", + "icon": "hypno-rings.png", + "type": "app", "tags": "rings,hypnosis,psychadelic", - "type":"app", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"hrings.app.js","url":"hypno-rings.js"}, {"name":"hrings.img","url":"hypno-rings-icon.js","evaluate":true} ] }, - { "id": "morse", + { + "id": "morse", "name": "Morse Code", - "icon": "morse-code.png", - "version":"0.01", + "version": "0.01", "description": "Learn morse code by hearing/seeing/feeling the code. Tap to toggle buzz!", + "icon": "morse-code.png", + "type": "app", "tags": "morse,sound,visual,input", - "type":"app", + "supports": ["BANGLEJS"], "storage": [ {"name":"morse.app.js","url":"morse-code.js"}, {"name":"morse.img","url":"morse-code-icon.js","evaluate":true} @@ -1039,97 +1208,112 @@ { "id": "blescan", "name": "BLE Scanner", - "icon": "blescan.png", - "version":"0.01", + "version": "0.01", "description": "Scan for advertising BLE devices", - "tags" : "bluetooth", - "storage" : [ + "icon": "blescan.png", + "tags": "bluetooth", + "supports": ["BANGLEJS"], + "storage": [ {"name":"blescan.app.js","url":"blescan.js"}, - {"name":"blescan.img","url":"blescan-icon.js", "evaluate":true} + {"name":"blescan.img","url":"blescan-icon.js","evaluate":true} ] }, - { "id": "mmonday", - "name": "Manic Monday Tone", - "icon": "manic-monday-icon.png", - "version":"0.02", - "description": "The Bangles make a comeback", - "tags": "sound", - "storage": [ - {"name":"mmonday.app.js","url":"manic-monday.js"}, - {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true} - ] - }, - { "id": "jbells", - "name": "Jingle Bells", - "icon": "jbells.png", - "version":"0.01", - "description": "Play Jingle Bells", + { + "id": "mmonday", + "name": "Manic Monday Tone", + "version": "0.02", + "description": "The Bangles make a comeback", + "icon": "manic-monday-icon.png", "tags": "sound", - "type":"app", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"mmonday.app.js","url":"manic-monday.js"}, + {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true} + ] + }, + { + "id": "jbells", + "name": "Jingle Bells", + "version": "0.01", + "description": "Play Jingle Bells", + "icon": "jbells.png", + "type": "app", + "tags": "sound", + "supports": ["BANGLEJS"], "storage": [ {"name":"jbells.app.js","url":"jbells.js"}, {"name":"jbells.img","url":"jbells-icon.js","evaluate":true} ] }, - { "id": "scolor", + { + "id": "scolor", "name": "Show Color", - "icon": "show-color.png", - "version":"0.01", + "version": "0.01", "description": "Display all available Colors and Names", + "icon": "show-color.png", + "type": "app", "tags": "tool", - "type":"app", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"scolor.app.js","url":"show-color.js"}, {"name":"scolor.img","url":"show-color-icon.js","evaluate":true} ] }, - { "id": "miclock", + { + "id": "miclock", "name": "Mixed Clock", - "icon": "clock-mixed.png", - "version":"0.05", + "version": "0.05", "description": "A mix of analog and digital Clock", + "icon": "clock-mixed.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"miclock.app.js","url":"clock-mixed.js"}, {"name":"miclock.img","url":"clock-mixed-icon.js","evaluate":true} ] }, - { "id": "bclock", + { + "id": "bclock", "name": "Binary Clock", - "icon": "clock-binary.png", - "version":"0.03", + "version": "0.03", "description": "A simple binary clock watch face", + "icon": "clock-binary.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"bclock.app.js","url":"clock-binary.js"}, {"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true} ] }, - { "id": "clotris", + { + "id": "clotris", "name": "Clock-Tris", - "icon": "clock-tris.png", - "version":"0.01", + "version": "0.01", "description": "A fully functional clone of a classic game of falling blocks", + "icon": "clock-tris.png", "tags": "game", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"clotris.app.js","url":"clock-tris.js"}, {"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true}, {"name":".trishig","url":"clock-tris-high"} ] }, - { "id": "flappy", + { + "id": "flappy", "name": "Flappy Bird", - "icon": "app.png", - "version":"0.05", + "version": "0.05", "description": "A Flappy Bird game clone", - "tags": "game,b2", - "allow_emulator":true, + "icon": "app.png", + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"flappy.app.js","url":"app.js"}, {"name":"flappy.img","url":"app-icon.js","evaluate":true} @@ -1138,137 +1322,159 @@ { "id": "gpsinfo", "name": "GPS Info", - "icon": "gps-info.png", - "version":"0.05", + "version": "0.05", "description": "An application that displays information about altitude, lat/lon, satellites and time", - "tags": "gps,b2", + "icon": "gps-info.png", "type": "app", + "tags": "gps", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"gpsinfo.app.js","url": "gps-info.js"}, - {"name":"gpsinfo.img","url": "gps-info-icon.js","evaluate": true} + {"name":"gpsinfo.app.js","url":"gps-info.js"}, + {"name":"gpsinfo.img","url":"gps-info-icon.js","evaluate":true} ] }, - { "id": "assistedgps", + { + "id": "assistedgps", "name": "Assisted GPS Update (AGPS)", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", - "custom": "custom.html", - "tags": "tool,outdoors,agps,bno2", + "icon": "app.png", "type": "RAM", - "storage": [ ] + "tags": "tool,outdoors,agps", + "supports": ["BANGLEJS"], + "custom": "custom.html", + "storage": [ + + ] }, { "id": "pomodo", - "name":"Pomodoro", - "icon":"pomodoro.png", - "version":"0.01", + "name": "Pomodoro", + "version": "0.01", "description": "A simple pomodoro timer.", - "tags": "pomodoro,cooking,tools", + "icon": "pomodoro.png", "type": "app", - "allow_emulator":true, + "tags": "pomodoro,cooking,tools", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ - {"name":"pomodo.app.js","url": "pomodoro.js"}, - {"name":"pomodo.img","url": "pomodoro-icon.js","evaluate": true} + {"name":"pomodo.app.js","url":"pomodoro.js"}, + {"name":"pomodo.img","url":"pomodoro-icon.js","evaluate":true} ] }, - { "id": "blobclk", + { + "id": "blobclk", "name": "Large Digit Blob Clock", - "shortName" : "Blob Clock", - "icon": "clock-blob.png", - "version":"0.06", + "shortName": "Blob Clock", + "version": "0.06", "description": "A clock with big digits", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-blob.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"blobclk.app.js","url":"clock-blob.js"}, {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true} ] }, - { "id": "boldclk", + { + "id": "boldclk", "name": "Bold Clock", - "icon": "bold_clock.png", - "version":"0.05", + "version": "0.05", "description": "Simple, readable and practical clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "bold_clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"boldclk.app.js","url":"bold_clock.js"}, {"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true} ] }, - { "id": "widclk", + { + "id": "widclk", "name": "Digital clock widget", - "icon": "widget.png", - "version":"0.06", + "version": "0.06", "description": "A simple digital clock widget", + "icon": "widget.png", + "type": "widget", "tags": "widget,clock", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widclk.wid.js","url":"widget.js"} ] }, - { "id": "widpedom", + { + "id": "widpedom", "name": "Pedometer widget", - "icon": "widget.png", - "version":"0.19", + "version": "0.19", "description": "Daily pedometer widget", - "tags": "widget,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widpedom.wid.js","url":"widget.js"}, {"name":"widpedom.settings.js","url":"settings.js"} ] }, - { "id": "berlinc", + { + "id": "berlinc", "name": "Berlin Clock", - "icon": "berlin-clock.png", - "version":"0.04", + "version": "0.05", "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", + "icon": "berlin-clock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"berlinc.app.js","url":"berlin-clock.js"}, {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true} ] }, - { "id": "ctrclk", + { + "id": "ctrclk", "name": "Centerclock", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "Watch-centered digital 24h clock with date in dd.mm.yyyy format.", - "tags": "clock,bno2", - "type":"clock", - "allow_emulator":true, + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"ctrclk.app.js","url":"app.js"}, {"name":"ctrclk.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "demoapp", + { + "id": "demoapp", "name": "Demo Loop", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", - "tags": "bno2", - "type":"app", - "allow_emulator":true, + "icon": "app.png", + "type": "app", + "tags": "", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.img","url":"app-icon.js","evaluate":true} ], - "sortorder" : -9 + "sortorder": -9 }, - { "id": "flagrse", + { + "id": "flagrse", "name": "Espruino Flag Raiser", - "icon": "app.png", - "version":"0.01", - "readme": "README.md", + "version": "0.01", "description": "App to send a command to another Espruino to cause it to raise a flag", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"flagrse.app.js","url":"app.js"}, {"name":"flagrse.img","url":"app-icon.js","evaluate":true} @@ -1277,64 +1483,73 @@ { "id": "pipboy", "name": "Pipboy", - "icon": "app.png", "version": "0.04", "description": "Pipboy themed clock", - "tags": "clock,bno2", - "type":"clock", - "allow_emulator":true, + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"pipboy.app.js","url":"app.js"}, {"name":"pipboy.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "torch", + { + "id": "torch", "name": "Torch", - "shortName":"Torch", - "icon": "app.png", - "version":"0.02", + "shortName": "Torch", + "version": "0.02", "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets", + "icon": "app.png", "tags": "tool,torch", + "supports": ["BANGLEJS"], "storage": [ {"name":"torch.app.js","url":"app.js"}, {"name":"torch.wid.js","url":"widget.js"}, {"name":"torch.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "rtorch", + { + "id": "rtorch", "name": "Red Torch", - "shortName":"RedTorch", - "icon": "app.png", - "version":"0.01", + "shortName": "RedTorch", + "version": "0.01", "description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets", + "icon": "app.png", "tags": "tool,torch", + "supports": ["BANGLEJS"], "storage": [ {"name":"rtorch.app.js","url":"app.js"}, {"name":"rtorch.wid.js","url":"widget.js"}, {"name":"rtorch.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "wohrm", + { + "id": "wohrm", "name": "Workout HRM", - "icon": "app.png", - "version":"0.08", - "readme": "README.md", + "version": "0.08", "description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.", - "tags": "hrm,workout", + "icon": "app.png", "type": "app", - "allow_emulator":true, + "tags": "hrm,workout", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"wohrm.app.js","url":"app.js"}, {"name":"wohrm.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widid", + { + "id": "widid", "name": "Bluetooth ID Widget", - "icon": "widget.png", - "version":"0.03", + "version": "0.03", "description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!", + "icon": "widget.png", + "type": "widget", "tags": "widget,address,mac", - "type":"widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widid.wid.js","url":"widget.js"} ] @@ -1342,113 +1557,130 @@ { "id": "grocery", "name": "Grocery", - "icon": "grocery.png", - "version":"0.02", + "version": "0.02", "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.", - "tags": "tool,outdoors,shopping,list", + "icon": "grocery.png", "type": "app", - "custom":"grocery.html", + "tags": "tool,outdoors,shopping,list", + "supports": ["BANGLEJS"], + "custom": "grocery.html", "storage": [ - {"name":"grocery.app.js", "url":"app.js"}, + {"name":"grocery.app.js","url":"app.js"}, {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} ] }, - { "id": "marioclock", + { + "id": "marioclock", "name": "Mario Clock", - "icon": "marioclock.png", - "version":"0.15", + "version": "0.15", "description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.", - "tags": "clock,mario,retro", + "icon": "marioclock.png", "type": "clock", - "allow_emulator":false, + "tags": "clock,mario,retro", + "supports": ["BANGLEJS"], "readme": "README.md", + "allow_emulator": false, "storage": [ {"name":"marioclock.app.js","url":"marioclock-app.js"}, {"name":"marioclock.img","url":"marioclock-icon.js","evaluate":true} ] }, - { "id": "cliock", + { + "id": "cliock", "name": "Commandline-Clock", - "shortName":"CLI-Clock", - "icon": "app.png", - "version":"0.13", + "shortName": "CLI-Clock", + "version": "0.14", "description": "Simple CLI-Styled Clock", - "tags": "clock,cli,command,bash,shell,b2", - "type":"clock", - "allow_emulator":true, + "icon": "app.png", + "type": "clock", + "tags": "clock,cli,command,bash,shell", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"cliock.app.js","url":"app.js"}, {"name":"cliock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widver", + { + "id": "widver", "name": "Firmware Version Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Display the version of the installed firmware in the top widget section.", + "icon": "widget.png", + "type": "widget", "tags": "widget,tool,system", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widver.wid.js","url":"widget.js"} ] }, - { "id": "barclock", + { + "id": "barclock", "name": "Bar Clock", - "icon": "clock-bar.png", - "version":"0.08", + "version": "0.08", "description": "A simple digital clock showing seconds as a bar", + "icon": "clock-bar.png", + "type": "clock", "tags": "clock", - "type":"clock", + "supports": ["BANGLEJS"], "readme": "README.md", - "allow_emulator":true, + "allow_emulator": true, "storage": [ {"name":"barclock.app.js","url":"clock-bar.js"}, {"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true} ] }, - { "id": "dotclock", + { + "id": "dotclock", "name": "Dot Clock", - "icon": "clock-dot.png", - "version":"0.03", + "version": "0.03", "description": "A Minimal Dot Analog Clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-dot.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"dotclock.app.js","url":"clock-dot.js"}, {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} ] }, - { "id": "widtbat", + { + "id": "widtbat", "name": "Tiny Battery Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.02", "description": "Tiny blueish battery widget, vibs and changes level color when charging", + "icon": "widget.png", + "type": "widget", "tags": "widget,tool,system", - "type":"widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widtbat.wid.js","url":"widget.js"} ] }, - { "id": "chrono", + { + "id": "chrono", "name": "Chrono", - "shortName":"Chrono", - "icon": "chrono.png", - "version":"0.01", + "shortName": "Chrono", + "version": "0.01", "description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.", + "icon": "chrono.png", "tags": "tool", + "supports": ["BANGLEJS"], "storage": [ {"name":"chrono.app.js","url":"chrono.js"}, {"name":"chrono.img","url":"chrono-icon.js","evaluate":true} ] }, - { "id": "astrocalc", + { + "id": "astrocalc", "name": "Astrocalc", - "icon": "astrocalc.png", - "version":"0.02", + "version": "0.02", "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", + "icon": "astrocalc.png", "tags": "app,sun,moon,cycles,tool,outdoors", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, {"name":"suncalc.js","url":"suncalc.js"}, @@ -1463,108 +1695,121 @@ {"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true} ] }, - { "id": "widhwt", + { + "id": "widhwt", "name": "Hand Wash Timer", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Swipe your wrist over the watch face to start your personal Bangle.js hand wash timer for 35 sec. Start washing after the short buzz and stop after the long buzz.", + "icon": "widget.png", + "type": "widget", "tags": "widget,tool", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widhwt.wid.js","url":"widget.js"} ] }, - { "id": "toucher", + { + "id": "toucher", "name": "Touch Launcher", - "shortName":"Toucher", - "icon": "app.png", - "version":"0.07", + "shortName": "Toucher", + "version": "0.07", "description": "Touch enable left to right launcher.", - "tags": "tool,system,launcher,b2", - "type":"launch", + "icon": "app.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", - "data": [ - {"name":"toucher.json"} - ], "storage": [ {"name":"toucher.app.js","url":"app.js"}, {"name":"toucher.settings.js","url":"settings.js"} ], - "sortorder" : -10 + "data": [{"name":"toucher.json"}], + "sortorder": -10 }, { "id": "balltastic", "name": "Balltastic", - "icon": "app.png", "version": "0.02", "description": "Simple but fun ball eats dots game.", - "tags": "game,fun", + "icon": "app.png", "type": "app", + "tags": "game,fun", + "supports": ["BANGLEJS"], "storage": [ - {"name":"balltastic.app.js","url":"app.js"}, - {"name":"balltastic.img","url":"app-icon.js","evaluate":true} - ] + {"name":"balltastic.app.js","url":"app.js"}, + {"name":"balltastic.img","url":"app-icon.js","evaluate":true} + ] }, { "id": "rpgdice", "name": "RPG dice", - "icon": "rpgdice.png", "version": "0.02", "description": "Simple RPG dice rolling app.", - "tags": "game,fun", + "icon": "rpgdice.png", "type": "app", + "tags": "game,fun", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ - {"name":"rpgdice.app.js","url": "app.js"}, - {"name":"rpgdice.img","url": "app-icon.js","evaluate":true} + {"name":"rpgdice.app.js","url":"app.js"}, + {"name":"rpgdice.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widmp", + { + "id": "widmp", "name": "Moon Phase Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases", + "icon": "widget.png", + "type": "widget", "tags": "widget,tools", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widmp.wid.js","url":"widget.js"} ] }, - { "id": "minionclk", + { + "id": "minionclk", "name": "Minion clock", - "icon": "minionclk.png", "version": "0.05", "description": "Minion themed clock.", - "tags": "clock,minion", + "icon": "minionclk.png", "type": "clock", + "tags": "clock,minion", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ {"name":"minionclk.app.js","url":"app.js"}, {"name":"minionclk.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "openstmap", + { + "id": "openstmap", "name": "OpenStreetMap", - "shortName":"OpenStMap", - "icon": "app.png", - "version":"0.09", + "shortName": "OpenStMap", + "version": "0.09", "description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are", - "tags": "outdoors,gps,b2", - "custom": "custom.html", "customConnect":true, + "icon": "app.png", + "tags": "outdoors,gps", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, "storage": [ {"name":"openstmap","url":"openstmap.js"}, {"name":"openstmap.app.js","url":"app.js"}, {"name":"openstmap.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "activepedom", + { + "id": "activepedom", "name": "Active Pedometer", - "shortName":"Active Pedometer", - "icon": "app.png", - "version":"0.09", + "shortName": "Active Pedometer", + "version": "0.09", "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", + "icon": "app.png", "tags": "outdoors,widget", - "readme": "README.md", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"activepedom.wid.js","url":"widget.js"}, {"name":"activepedom.settings.js","url":"settings.js"}, @@ -1572,136 +1817,154 @@ {"name":"activepedom.app.js","url":"app.js"} ] }, - { "id": "chronowid", + { + "id": "chronowid", "name": "Chrono Widget", - "shortName":"Chrono Widget", - "icon": "app.png", - "version":"0.03", + "shortName": "Chrono Widget", + "version": "0.03", "description": "Chronometer (timer) which runs as widget.", - "tags": "tool,widget,b2", - "readme": "README.md", + "icon": "app.png", + "tags": "tool,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "storage": [ {"name":"chronowid.wid.js","url":"widget.js"}, {"name":"chronowid.app.js","url":"app.js"}, {"name":"chronowid.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "tabata", + { + "id": "tabata", "name": "Tabata", "shortName": "Tabata - Control High-Intensity Interval Training", - "icon": "tabata.png", - "version":"0.01", + "version": "0.01", "description": "Control high-intensity interval training (according to tabata: https://en.wikipedia.org/wiki/Tabata_method).", + "icon": "tabata.png", "tags": "workout,health", + "supports": ["BANGLEJS"], "storage": [ {"name":"tabata.app.js","url":"tabata.js"}, {"name":"tabata.img","url":"tabata-icon.js","evaluate":true} ] }, - { "id": "custom", + { + "id": "custom", "name": "Custom Boot Code ", - "icon": "custom.png", - "version":"0.01", + "version": "0.01", "description": "Add code you want to run at boot time", - "tags": "tool,system", + "icon": "custom.png", "type": "bootloader", - "custom":"custom.html", + "tags": "tool,system", + "supports": ["BANGLEJS"], + "custom": "custom.html", "storage": [ - {"name":"custom"} + {"name":"custom"} ] }, - { "id": "devstopwatch", - "name": "Dev Stopwatch", - "shortName":"Dev Stopwatch", - "icon": "app.png", - "version":"0.03", - "description": "Stopwatch with 5 laps supported (cyclically replaced)", - "tags": "stopwatch,chrono,timer,chronometer,b2", - "allow_emulator":true, - "storage": [ - {"name":"devstopwatch.app.js","url":"app.js"}, - {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "batchart", - "name": "Battery Chart", - "shortName":"Battery Chart", + { + "id": "devstopwatch", + "name": "Dev Stopwatch", + "shortName": "Dev Stopwatch", + "version": "0.03", + "description": "Stopwatch with 5 laps supported (cyclically replaced)", "icon": "app.png", - "version":"0.10", - "readme": "README.md", + "tags": "stopwatch,chrono,timer,chronometer", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"devstopwatch.app.js","url":"app.js"}, + {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "batchart", + "name": "Battery Chart", + "shortName": "Battery Chart", + "version": "0.10", "description": "A widget and an app for recording and visualizing battery percentage over time.", + "icon": "app.png", "tags": "app,widget,battery,time,record,chart,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"batchart.wid.js","url":"widget.js"}, {"name":"batchart.app.js","url":"app.js"}, {"name":"batchart.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "nato", + { + "id": "nato", "name": "NATO Alphabet", - "shortName" : "NATOAlphabet", - "icon": "nato.png", - "version":"0.01", - "type": "app", + "shortName": "NATOAlphabet", + "version": "0.01", "description": "Learn the NATO Phonetic alphabet plus some numbers.", + "icon": "nato.png", + "type": "app", "tags": "app,learn,visual", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"nato.app.js","url":"nato.js"}, {"name":"nato.img","url":"nato-icon.js","evaluate":true} ] }, - { "id": "numerals", + { + "id": "numerals", "name": "Numerals Clock", "shortName": "Numerals Clock", - "icon": "numerals.png", - "version":"0.09", + "version": "0.09", "description": "A simple big numerals clock", + "icon": "numerals.png", + "type": "clock", "tags": "numerals,clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"numerals.app.js","url":"numerals.app.js"}, {"name":"numerals.img","url":"numerals-icon.js","evaluate":true}, {"name":"numerals.settings.js","url":"numerals.settings.js"} ], - "data":[ - {"name":"numerals.json"} - ] + "data": [{"name":"numerals.json"}] }, - { "id": "bledetect", + { + "id": "bledetect", "name": "BLE Detector", - "shortName":"BLE Detector", - "icon": "bledetect.png", - "version":"0.03", + "shortName": "BLE Detector", + "version": "0.03", "description": "Detect BLE devices and show some informations.", + "icon": "bledetect.png", "tags": "app,bluetooth,tool", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"bledetect.app.js","url":"bledetect.js"}, {"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true} ] }, - { "id": "snake", + { + "id": "snake", "name": "Snake", - "shortName":"Snake", - "icon": "snake.png", - "version":"0.02", + "shortName": "Snake", + "version": "0.02", "description": "The classic snake game. Eat apples and don't bite your tail.", + "icon": "snake.png", "tags": "game,fun", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"snake.app.js","url":"snake.js"}, {"name":"snake.img","url":"snake-icon.js","evaluate":true} ] }, - { "id": "calculator", + { + "id": "calculator", "name": "Calculator", - "shortName":"Calculator", - "icon": "calculator.png", - "version":"0.04", + "shortName": "Calculator", + "version": "0.04", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", - "tags": "app,tool,b2", + "icon": "calculator.png", + "tags": "app,tool", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"calculator.app.js","url":"app.js"}, {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} @@ -1711,54 +1974,49 @@ "id": "dane", "name": "Digital Assistant, not EDITH", "shortName": "DANE", - "icon": "app.png", "version": "0.16", "description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ - { - "name": "dane.app.js", - "url": "app.js" - }, - { - "name": "dane.img", - "url": "app-icon.js", - "evaluate": true - } + {"name":"dane.app.js","url":"app.js"}, + {"name":"dane.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "dane_tcr", + { + "id": "dane_tcr", "name": "DANE Touch Launcher", - "shortName":"DANE Toucher", - "icon": "app.png", - "version":"0.07", + "shortName": "DANE Toucher", + "version": "0.07", "description": "Touch enable left to right launcher in the style of the DANE Watchface", + "icon": "app.png", + "type": "launch", "tags": "tool,system,launcher", - "type":"launch", - "data": [ - {"name":"dane_tcr.json"} - ], + "supports": ["BANGLEJS"], "storage": [ {"name":"dane_tcr.app.js","url":"app.js"}, {"name":"dane_tcr.settings.js","url":"settings.js"} ], - "sortorder" : -10 + "data": [{"name":"dane_tcr.json"}], + "sortorder": -10 }, { "id": "buffgym", "name": "BuffGym", - "icon": "buffgym.png", - "version":"0.02", + "version": "0.02", "description": "BuffGym is the famous 5x5 workout program for the BangleJS", - "tags": "tool,outdoors,gym,exercise", + "icon": "buffgym.png", "type": "app", + "tags": "tool,outdoors,gym,exercise", + "supports": ["BANGLEJS"], + "readme": "README.md", "interface": "buffgym.html", "allow_emulator": false, - "readme": "README.md", "storage": [ - {"name":"buffgym.app.js", "url": "buffgym.app.js"}, + {"name":"buffgym.app.js","url":"buffgym.app.js"}, {"name":"buffgym-set.js","url":"buffgym-set.js"}, {"name":"buffgym-exercise.js","url":"buffgym-exercise.js"}, {"name":"buffgym-workout.js","url":"buffgym-workout.js"}, @@ -1772,82 +2030,76 @@ "id": "banglerun", "name": "BangleRun", "shortName": "BangleRun", - "icon": "banglerun.png", "version": "0.10", - "interface": "interface.html", "description": "An app for running sessions. Displays info and logs your run for later viewing.", + "icon": "banglerun.png", "tags": "run,running,fitness,outdoors", + "supports": ["BANGLEJS"], + "interface": "interface.html", "allow_emulator": false, "storage": [ - { - "name": "banglerun.app.js", - "url": "app.js" - }, - { - "name": "banglerun.img", - "url": "app-icon.js", - "evaluate": true - } + {"name":"banglerun.app.js","url":"app.js"}, + {"name":"banglerun.img","url":"app-icon.js","evaluate":true} ] }, { "id": "metronome", "name": "Metronome", - "icon": "metronome_icon.png", "version": "0.06", - "readme": "README.md", "description": "Makes the watch blinking and vibrating with a given rate", + "icon": "metronome_icon.png", "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "metronome.app.js", - "url": "metronome.js" - }, - { - "name": "metronome.img", - "url": "metronome-icon.js", - "evaluate": true - }, + {"name":"metronome.app.js","url":"metronome.js"}, + {"name":"metronome.img","url":"metronome-icon.js","evaluate":true}, {"name":"metronome.settings.js","url":"settings.js"} ] }, - { "id": "blackjack", + { + "id": "blackjack", "name": "Black Jack game", - "shortName":"Black Jack game", - "icon": "blackjack.png", - "version":"0.02", + "shortName": "Black Jack game", + "version": "0.02", "description": "Simple implementation of card game Black Jack", + "icon": "blackjack.png", "tags": "game", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"blackjack.app.js","url":"blackjack.app.js"}, {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true} ] }, - { "id": "hidcam", + { + "id": "hidcam", "name": "Camera shutter", - "shortName":"Cam shutter", - "icon": "app.png", - "version":"0.03", + "shortName": "Cam shutter", + "version": "0.03", "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle", - "readme": "README.md", + "icon": "app.png", "tags": "bluetooth,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ - {"name":"hidcam.app.js","url":"app.js"}, - {"name":"hidcam.img","url":"app-icon.js","evaluate":true} + {"name":"hidcam.app.js","url":"app.js"}, + {"name":"hidcam.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "swlclk", + { + "id": "swlclk", "name": "SWL Clock / Short Wave Listner Clock", "shortName": "SWL Clock", - "icon": "swlclk.png", - "version":"0.02", + "version": "0.02", "description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies", + "icon": "swlclk.png", + "type": "clock", "tags": "tool,clock", - "type":"clock", + "supports": ["BANGLEJS"], "readme": "README.md", - "allow_emulator":true, + "allow_emulator": true, "storage": [ {"name":"swlclk.app.js","url":"app.js"}, {"name":"swlclk.img","url":"app-icon.js","evaluate":true} @@ -1857,11 +2109,12 @@ "id": "rclock", "name": "Round clock with seconds, minutes and date", "shortName": "Round Clock", - "icon": "app.png", "version": "0.06", "description": "Designed round clock with ticks for minutes and seconds and heart rate indication", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"rclock.app.js","url":"rclock.app.js"}, {"name":"rclock.img","url":"app-icon.js","evaluate":true} @@ -1871,35 +2124,40 @@ "id": "fclock", "name": "fclock", "shortName": "F Clock", - "icon": "app.png", "version": "0.02", "description": "Simple design of a digital clock", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"fclock.app.js","url":"fclock.app.js"}, {"name":"fclock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "hamloc", + { + "id": "hamloc", "name": "QTH Locator / Maidenhead Locator System", "shortName": "QTH Locator", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Convert your current GPS location to the Maidenhead locator system used by HAM amateur radio operators", + "icon": "app.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"hamloc.app.js","url":"app.js"}, {"name":"hamloc.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "osmpoi", + { + "id": "osmpoi", "name": "POI Compass", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "Uploads all the points of interest in an area onto your watch, same as Beer Compass with more p.o.i.", + "icon": "app.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "readme": "README.md", "custom": "custom.html", "storage": [ @@ -1907,64 +2165,63 @@ {"name":"osmpoi.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "pong", + { + "id": "pong", "name": "Pong", "shortName": "Pong", - "icon": "pong.png", "version": "0.03", "description": "A clone of the Atari game Pong", - "tags": "game", + "icon": "pong.png", "type": "app", - "allow_emulator": true, + "tags": "game", + "supports": ["BANGLEJS"], "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"pong.app.js","url":"app.js"}, {"name":"pong.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "ballmaze", + { + "id": "ballmaze", "name": "Ball Maze", - "icon": "icon.png", "version": "0.02", "description": "Navigate a ball through a maze by tilting your watch.", - "readme": "README.md", - "tags": "game", + "icon": "icon.png", "type": "app", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ - {"name": "ballmaze.app.js","url":"app.js"}, - {"name": "ballmaze.img","url":"icon.js","evaluate": true} + {"name":"ballmaze.app.js","url":"app.js"}, + {"name":"ballmaze.img","url":"icon.js","evaluate":true} ], - "data": [ - {"name": "ballmaze.json"} - ] + "data": [{"name":"ballmaze.json"}] }, - { "id": "calendar", + { + "id": "calendar", "name": "Calendar", - "icon": "calendar.png", "version": "0.02", "description": "Simple calendar", - "tags": "calendar,b2", + "icon": "calendar.png", + "tags": "calendar", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "calendar.app.js", - "url": "calendar.js" - }, - { - "name": "calendar.img", - "url": "calendar-icon.js", - "evaluate": true - } + {"name":"calendar.app.js","url":"calendar.js"}, + {"name":"calendar.img","url":"calendar-icon.js","evaluate":true} ] }, - { "id": "hidjoystick", + { + "id": "hidjoystick", "name": "Bluetooth Joystick", "shortName": "Joystick", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.", + "icon": "app.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidjoystick.app.js","url":"app.js"}, {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true} @@ -1973,30 +2230,31 @@ { "id": "largeclock", "name": "Large Clock", - "icon": "largeclock.png", "version": "0.10", "description": "A readable and informational digital watch, with date, seconds and moon phase", - "readme": "README.md", - "tags": "clock", + "icon": "largeclock.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", "allow_emulator": true, "storage": [ - {"name": "largeclock.app.js", "url": "largeclock.js"}, - {"name": "largeclock.img", "url": "largeclock-icon.js", "evaluate": true}, - {"name": "largeclock.settings.js", "url": "settings.js"} + {"name":"largeclock.app.js","url":"largeclock.js"}, + {"name":"largeclock.img","url":"largeclock-icon.js","evaluate":true}, + {"name":"largeclock.settings.js","url":"settings.js"} ], - "data": [ - {"name":"largeclock.json"} - ] + "data": [{"name":"largeclock.json"}] }, - { "id": "smtswch", + { + "id": "smtswch", "name": "Smart Switch", - "shortName":"Smart Switch", - "icon": "app.png", - "version":"0.01", + "shortName": "Smart Switch", + "version": "0.01", "description": "Using EspruinoHub, control your smart devices on and off via Bluetooth Low Energy!", - "tags": "bluetooth,btle,smart,switch", + "icon": "app.png", "type": "app", + "tags": "bluetooth,btle,smart,switch", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"smtswch.app.js","url":"app.js"}, @@ -2007,13 +2265,15 @@ {"name":"switch-off.img","url":"switch-off.js","evaluate":true} ] }, - { "id": "miplant", + { + "id": "miplant", "name": "Xiaomi Plant Sensor", - "shortName":"Mi Plant", - "icon": "app.png", - "version":"0.02", + "shortName": "Mi Plant", + "version": "0.02", "description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors", + "icon": "app.png", "tags": "xiaomi,mi,plant,ble,bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"miplant.app.js","url":"app.js"}, {"name":"miplant.img","url":"app-icon.js","evaluate":true} @@ -2022,76 +2282,63 @@ { "id": "simpletimer", "name": "Timer", - "icon": "app.png", "version": "0.07", "description": "Simple timer, useful when playing board games or cooking", + "icon": "app.png", "tags": "timer", + "supports": ["BANGLEJS"], "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "simpletimer.app.js", - "url": "app.js" - }, - { - "name": ".tfnames", - "url": "gesture-tfnames.js", - "evaluate": true - }, - { - "name": ".tfmodel", - "url": "gesture-tfmodel.js", - "evaluate": true - }, - { - "name": "simpletimer.img", - "url": "app-icon.js", - "evaluate": true - } + {"name":"simpletimer.app.js","url":"app.js"}, + {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true}, + {"name":".tfmodel","url":"gesture-tfmodel.js","evaluate":true}, + {"name":"simpletimer.img","url":"app-icon.js","evaluate":true} ], - "data": [ - { - "name": "simpletimer.json" - } - ] + "data": [{"name":"simpletimer.json"}] }, { "id": "beebclock", "name": "Beeb Clock", - "icon": "beebclock.png", - "version":"0.05", + "version": "0.05", "description": "Clock face that may be coincidentally familiar to BBC viewers", - "tags": "clock", + "icon": "beebclock.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ - {"name":"beebclock.app.js","url":"beebclock.js"}, - {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true} + {"name":"beebclock.app.js","url":"beebclock.js"}, + {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true} ] }, - { "id": "findphone", + { + "id": "findphone", "name": "Find Phone", - "shortName":"Find Phone", - "icon": "app.png", - "version":"0.03", + "shortName": "Find Phone", + "version": "0.03", "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.", + "icon": "app.png", "tags": "tool,android", + "supports": ["BANGLEJS"], "readme": "README.md", "allow_emulator": true, "storage": [ - {"name":"findphone.app.js","url":"app.js"}, - {"name":"findphone.img","url":"app-icon.js","evaluate":true} + {"name":"findphone.app.js","url":"app.js"}, + {"name":"findphone.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "getup", + { + "id": "getup", "name": "Get Up", - "shortName":"Get Up", - "icon": "app.png", - "version":"0.01", + "shortName": "Get Up", + "version": "0.01", "description": "Reminds you to getup every x minutes. Sitting to long is dangerous!", + "icon": "app.png", "tags": "tools,health", + "supports": ["BANGLEJS"], "readme": "README.md", - "allow_emulator":true, + "allow_emulator": true, "storage": [ {"name":"getup.app.js","url":"app.js"}, {"name":"getup.settings.js","url":"settings.js"}, @@ -2102,43 +2349,46 @@ "id": "gallifr", "name": "Time Traveller's Chronometer", "shortName": "Time Travel Clock", - "icon": "gallifr.png", "version": "0.02", "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.", - "tags": "clock,b2", - "readme": "README.md", + "icon": "gallifr.png", "type": "clock", - "allow_emulator":true, - "storage": [ - { "name": "gallifr.app.js", "url": "app.js" }, - { "name": "gallifr.img", "url": "app-icon.js", "evaluate": true }, - { "name": "gallifr.settings.js", "url": "settings.js" } - ], - "data": [ - {"name":"gallifr.json"} - ] - }, - { "id": "rndmclk", - "name": "Random Clock Loader", - "icon": "rndmclk.png", - "version":"0.03", - "description": "Load a different clock whenever the LCD is switched on.", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"gallifr.app.js","url":"app.js"}, + {"name":"gallifr.img","url":"app-icon.js","evaluate":true}, + {"name":"gallifr.settings.js","url":"settings.js"} + ], + "data": [{"name":"gallifr.json"}] + }, + { + "id": "rndmclk", + "name": "Random Clock Loader", + "version": "0.03", + "description": "Load a different clock whenever the LCD is switched on.", + "icon": "rndmclk.png", + "type": "widget", "tags": "widget,clock", - "type":"widget", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"rndmclk.wid.js","url":"widget.js"} ] }, - { "id": "dotmatrixclock", + { + "id": "dotmatrixclock", "name": "Dotmatrix Clock", - "icon": "dotmatrixclock.png", - "version":"0.01", + "version": "0.01", "description": "A clear white-on-blue dotmatrix simulated clock", - "tags": "clock,dotmatrix,retro", + "icon": "dotmatrixclock.png", "type": "clock", - "allow_emulator":true, + "tags": "clock,dotmatrix,retro", + "supports": ["BANGLEJS"], "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"dotmatrixclock.app.js","url":"app.js"}, {"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true} @@ -2148,63 +2398,71 @@ "id": "jbm8b", "name": "Magic 8 Ball", "shortName": "Magic 8 Ball", - "icon": "app.png", - "description": "A simple fortune telling app", - "tags": "game", "version": "0.03", + "description": "A simple fortune telling app", + "icon": "app.png", + "tags": "game", + "supports": ["BANGLEJS"], "storage": [ - { "name": "jbm8b.app.js", "url": "app.js" }, - { "name": "jbm8b.img", "url": "app-icon.js", "evaluate": true } - ] + {"name":"jbm8b.app.js","url":"app.js"}, + {"name":"jbm8b.img","url":"app-icon.js","evaluate":true} + ] }, { "id": "jbm8b_IT", "name": "Magic 8 Ball Italiano", "shortName": "Magic 8 Ball IT", - "icon": "app.png", + "version": "0.01", "description": "La palla predice il futuro", + "icon": "app.png", "tags": "game", - "version": "0.01", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ - { "name": "jbm8b_IT.app.js", "url": "app.js" }, - { "name": "jbm8b_IT.img", "url": "app-icon.js", "evaluate": true } - ] - }, - { "id": "BLEcontroller", - "name": "BLE Customisable Controller with Joystick", - "shortName": "BLE Controller", - "icon": "BLEcontroller.png", - "version": "0.01", - "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.", - "tags": "tool,bluetooth", - "readme": "README.md", - "allow_emulator":false, - "storage": [ - { "name": "BLEcontroller.app.js", "url": "app.js" }, - { "name": "BLEcontroller.img", "url": "app-icon.js", "evaluate": true } + {"name":"jbm8b_IT.app.js","url":"app.js"}, + {"name":"jbm8b_IT.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widviz", + { + "id": "BLEcontroller", + "name": "BLE Customisable Controller with Joystick", + "shortName": "BLE Controller", + "version": "0.01", + "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.", + "icon": "BLEcontroller.png", + "tags": "tool,bluetooth", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"BLEcontroller.app.js","url":"app.js"}, + {"name":"BLEcontroller.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "widviz", "name": "Widget Visibility Widget", - "shortName":"Viz Widget", - "icon": "eye.png", - "version":"0.02", + "shortName": "Viz Widget", + "version": "0.02", "description": "Swipe left to hide top bar widgets, swipe right to redisplay.", - "tags": "widget", + "icon": "eye.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widviz.wid.js","url":"widget.js"} ] }, - { "id": "binclock", + { + "id": "binclock", "name": "Binary Clock", - "shortName":"Binary Clock", - "icon": "app.png", - "version":"0.03", + "shortName": "Binary Clock", + "version": "0.03", "description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.", - "tags": "clock,binary", + "icon": "app.png", "type": "clock", + "tags": "clock,binary", + "supports": ["BANGLEJS"], "storage": [ {"name":"binclock.app.js","url":"app.js"}, {"name":"binclock.img","url":"app-icon.js","evaluate":true} @@ -2213,25 +2471,28 @@ { "id": "pizzatimer", "name": "Pizza Timer", - "shortName":"Pizza Timer", - "icon": "pizza.png", - "version":"0.01", + "shortName": "Pizza Timer", + "version": "0.01", "description": "A timer app for when you cook Pizza. Some say it can also time other things", + "icon": "pizza.png", "tags": "timer,tool,pizza", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"pizzatimer.app.js","url":"app.js"}, {"name":"pizzatimer.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "animclk", + { + "id": "animclk", "name": "Animated Clock", - "shortName":"Anim Clock", - "icon": "app.png", - "version":"0.03", + "shortName": "Anim Clock", + "version": "0.03", "description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art", - "tags": "clock,animated,bno2", + "icon": "app.png", "type": "clock", + "tags": "clock,animated", + "supports": ["BANGLEJS"], "storage": [ {"name":"animclk.app.js","url":"app.js"}, {"name":"animclk.pixels1","url":"animclk.pixels1"}, @@ -2240,14 +2501,16 @@ {"name":"animclk.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "analogimgclk", + { + "id": "analogimgclk", "name": "Analog Clock (Image background)", - "shortName":"Analog Clock", - "icon": "app.png", - "version":"0.03", + "shortName": "Analog Clock", + "version": "0.03", "description": "An analog clock with an image background", - "tags": "clock,bno2", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"analogimgclk.app.js","url":"app.js"}, {"name":"analogimgclk.bg.img","url":"bg.img"}, @@ -2257,116 +2520,131 @@ { "id": "verticalface", "name": "Vertical watch face", - "shortName":"Vertical Face", - "icon": "app.png", - "version":"0.09", + "shortName": "Vertical Face", + "version": "0.09", "description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"verticalface.app.js","url":"app.js"}, {"name":"verticalface.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "sleepphasealarm", + { + "id": "sleepphasealarm", "name": "SleepPhaseAlarm", - "shortName":"SleepPhaseAlarm", - "icon": "app.png", - "version":"0.02", + "shortName": "SleepPhaseAlarm", + "version": "0.02", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", + "icon": "app.png", "tags": "alarm", + "supports": ["BANGLEJS"], "storage": [ {"name":"sleepphasealarm.app.js","url":"app.js"}, {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "life", + { + "id": "life", "name": "Game of Life", - "icon": "life.png", - "version":"0.04", + "version": "0.04", "description": "Conway's Game of Life - 16x16 board", + "icon": "life.png", "tags": "game", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"life.app.js","url":"life.min.js"}, {"name":"life.img","url":"life-icon.js","evaluate":true} ] }, - { "id": "magnav", + { + "id": "magnav", "name": "Navigation Compass", - "icon": "magnav.png", - "version":"0.04", + "version": "0.04", "description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.", - "readme": "README.md", + "icon": "magnav.png", "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"magnav.app.js","url":"magnav.min.js"}, {"name":"magnav.img","url":"magnav-icon.js","evaluate":true} ], - "data":[{"name":"magnav.json"}] + "data": [{"name":"magnav.json"}] }, - { "id": "gpspoilog", + { + "id": "gpspoilog", "name": "GPS POI Logger", - "shortName":"GPS POI Log", - "icon": "app.png", - "version":"0.01", + "shortName": "GPS POI Log", + "version": "0.01", "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial", + "icon": "app.png", "tags": "outdoors", + "supports": ["BANGLEJS"], "interface": "interface.html", "storage": [ {"name":"gpspoilog.app.js","url":"app.js"}, {"name":"gpspoilog.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "miclock2", + { + "id": "miclock2", "name": "Mixed Clock 2", - "icon": "clock-mixed.png", - "version":"0.01", + "version": "0.01", "description": "White color variant of the Mixed Clock with thicker clock hands for better readability in the bright sunlight, extra space under the clock for widgets and seconds in the digital clock.", + "icon": "clock-mixed.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"miclock2.app.js","url":"clock-mixed.js"}, {"name":"miclock2.img","url":"clock-mixed-icon.js","evaluate":true} ] }, - { "id": "1button", + { + "id": "1button", "name": "One-Button-Tracker", - "icon": "widget.png", - "version":"0.01", - "interface": "interface.html", + "version": "0.01", "description": "A widget that turns BTN1 into a tracker, records time of button press/release.", - "tags": "tool,quantifiedself,widget", + "icon": "widget.png", "type": "widget", + "tags": "tool,quantifiedself,widget", + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"1button.wid.js","url":"widget.js"} ], - "data": [ - {"name":"one_button_presses.csv","storageFile": true} - ] + "data": [{"name":"one_button_presses.csv","storageFile":true}] }, - { "id": "gpsautotime", + { + "id": "gpsautotime", "name": "GPS auto time", - "shortName":"GPS auto time", - "icon": "widget.png", - "version":"0.01", + "shortName": "GPS auto time", + "version": "0.01", "description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.", - "tags": "widget,gps", + "icon": "widget.png", "type": "widget", + "tags": "widget,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"gpsautotime.wid.js","url":"widget.js"} ] }, - { "id": "espruinoctrl", + { + "id": "espruinoctrl", "name": "Espruino Control", - "shortName":"Espruino Ctrl", - "icon": "app.png", - "version":"0.01", + "shortName": "Espruino Ctrl", + "version": "0.01", "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "readme": "README.md", "custom": "custom.html", "storage": [ @@ -2374,15 +2652,17 @@ {"name":"espruinoctrl.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "multiclock", + { + "id": "multiclock", "name": "Multi Clock", - "icon": "multiclock.png", - "version":"0.13", + "version": "0.13", "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3", - "readme": "README.md", + "icon": "multiclock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"multiclock.app.js","url":"clock.js"}, {"name":"big.face.js","url":"big.js"}, @@ -2394,152 +2674,157 @@ {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} ] }, - { "id": "widancs", + { + "id": "widancs", "name": "Apple Notification Widget", - "shortName":"ANCS Widget", - "icon": "widget.png", - "version":"0.07", + "shortName": "ANCS Widget", + "version": "0.07", "description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps", - "readme": "README.md", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"widancs.wid.js","url":"ancs.min.js"}, {"name":"widancs.settings.js","url":"settings.js"} ] }, - { "id": "accelrec", + { + "id": "accelrec", "name": "Acceleration Recorder", - "shortName":"Accel Rec", - "icon": "app.png", - "version":"0.02", - "interface": "interface.html", + "shortName": "Accel Rec", + "version": "0.02", "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"accelrec.app.js","url":"app.js"}, {"name":"accelrec.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"wildcard":"accelrec.?.csv" } - ] + "data": [{"wildcard":"accelrec.?.csv"}] }, - { "id": "accellog", + { + "id": "accellog", "name": "Acceleration Logger", - "shortName":"Accel Log", - "icon": "app.png", - "version":"0.03", - "interface": "interface.html", + "shortName": "Accel Log", + "version": "0.03", "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", - "tags": "outdoor,b2", + "icon": "app.png", + "tags": "outdoor", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"accellog.app.js","url":"app.js"}, {"name":"accellog.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"wildcard":"accellog.?.csv" } - ] + "data": [{"wildcard":"accellog.?.csv"}] }, { "id": "cprassist", - "name":"CPR Assist", - "icon":"cprassist-icon.png", + "name": "CPR Assist", "version": "0.01", - "readme": "README.md", "description": "Provides assistance while performing a CPR", + "icon": "cprassist-icon.png", "tags": "tool,firstaid", + "supports": ["BANGLEJS"], + "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "cprassist.app.js", - "url": "cprassist.js" - }, - { - "name": "cprassist.img", - "url": "cprassist-icon.js", - "evaluate": true - }, - { - "name": "cprassist.settings.js", - "url": "settings.js" - } + {"name":"cprassist.app.js","url":"cprassist.js"}, + {"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true}, + {"name":"cprassist.settings.js","url":"settings.js"} ] }, - { "id": "osgridref", + { + "id": "osgridref", "name": "Ordnance Survey Grid Reference", - "shortName":"OS Grid ref", - "icon": "app.png", - "version":"0.01", + "shortName": "OS Grid ref", + "version": "0.01", "description": "Displays the UK Ordnance Survey grid reference of your current GPS location. Useful when in the United Kingdom with an Ordnance Survey map", + "icon": "app.png", "tags": "outdoors,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"osgridref.app.js","url":"app.js"}, {"name":"osgridref.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "openseizure", + { + "id": "openseizure", "name": "OpenSeizureDetector Widget", - "shortName":"Short Name", - "icon": "widget.png", - "version":"0.01", + "shortName": "Short Name", + "version": "0.01", "description": "[BETA!] A widget to work alongside [OpenSeizureDetector](https://www.openseizuredetector.org.uk/)", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"openseizure.wid.js","url":"widget.js"} ] }, - {"id": "counter", - "name": "Counter", - "icon": "counter_icon.png", - "version": "0.03", - "description": "Simple counter", - "tags": "tool", - "allow_emulator": true, - "storage": [ - {"name": "counter.app.js", "url": "counter.js"}, - {"name": "counter.img", "url": "counter-icon.js", "evaluate": true} - ] + { + "id": "counter", + "name": "Counter", + "version": "0.03", + "description": "Simple counter", + "icon": "counter_icon.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"counter.app.js","url":"counter.js"}, + {"name":"counter.img","url":"counter-icon.js","evaluate":true} + ] }, - { "id": "bootgattbat", + { + "id": "bootgattbat", "name": "BLE GATT Battery Service", - "shortName":"BLE Battery Service", - "icon": "bluetooth.png", - "version":"0.01", + "shortName": "BLE Battery Service", + "version": "0.01", "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", - "tags": "battery,ble,bluetooth,gatt", + "icon": "bluetooth.png", "type": "bootloader", + "tags": "battery,ble,bluetooth,gatt", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"gattbat.boot.js","url":"boot.js"} ] }, - { "id": "viewstl", - "name": "STL file viewer", - "shortName":"ViewSTL", - "icon": "icons8-octahedron-48.png", - "version":"0.02", - "description": "This app allows you to view STL 3D models on your watch", - "tags": "tool", - "readme": "README.md", - "storage": [ - {"name":"viewstl.app.js","url":"viewstl.min.js"}, - {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true}, - {"name":"tetra.stl","url":"tetra.stl"}, - {"name":"cube.stl","url":"cube.stl"}, - {"name":"icosa.stl","url":"icosa.stl"} - ] + { + "id": "viewstl", + "name": "STL file viewer", + "shortName": "ViewSTL", + "version": "0.02", + "description": "This app allows you to view STL 3D models on your watch", + "icon": "icons8-octahedron-48.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"viewstl.app.js","url":"viewstl.min.js"}, + {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true}, + {"name":"tetra.stl","url":"tetra.stl"}, + {"name":"cube.stl","url":"cube.stl"}, + {"name":"icosa.stl","url":"icosa.stl"} + ] }, - { "id": "cscsensor", + { + "id": "cscsensor", "name": "Cycling speed sensor", - "shortName":"CSCSensor", - "icon": "icons8-cycling-48.png", - "version":"0.05", + "shortName": "CSCSensor", + "version": "0.05", "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", + "icon": "icons8-cycling-48.png", "tags": "outdoors,exercise,ble,bluetooth", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"cscsensor.app.js","url":"cscsensor.app.js"}, @@ -2547,70 +2832,78 @@ {"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true} ] }, - { "id": "fileman", + { + "id": "fileman", "name": "File manager", - "shortName":"FileManager", - "icon": "icons8-filing-cabinet-48.png", - "version":"0.03", + "shortName": "FileManager", + "version": "0.03", "description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files", + "icon": "icons8-filing-cabinet-48.png", "tags": "tools", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"fileman.app.js","url":"fileman.app.js"}, {"name":"fileman.img","url":"fileman-icon.js","evaluate":true} ] }, - { "id": "worldclock", + { + "id": "worldclock", "name": "World Clock - 4 time zones", - "shortName":"World Clock", - "icon": "app.png", - "version":"0.04", + "shortName": "World Clock", + "version": "0.05", "description": "Current time zone plus up to four others", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type" : "clock", - "custom": "custom.html", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "custom": "custom.html", "storage": [ {"name":"worldclock.app.js","url":"app.js"}, {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true} ], - "data": [ - {"name":"worldclock.settings.json"} + "data": [{"name":"worldclock.settings.json"}] + }, + { + "id": "digiclock", + "name": "Digital Clock Face", + "shortName": "Digi Clock", + "version": "0.02", + "description": "A simple digital clock with the time, day, month, and year", + "icon": "digiclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"digiclock.app.js","url":"digiclock.js"}, + {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} ] - }, -{ "id": "digiclock", - "name": "Digital Clock Face", - "shortName":"Digi Clock", - "icon": "digiclock.png", - "version":"0.02", - "description": "A simple digital clock with the time, day, month, and year", - "tags": "clock,bno2", - "type" : "clock", - "storage": [ - {"name":"digiclock.app.js","url":"digiclock.js"}, - {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} - ] -}, - { "id": "dsdrelay", + }, + { + "id": "dsdrelay", "name": "DSD BLE Relay controller", - "shortName":"DSDRelay", - "icon": "icons8-relay-48.png", - "version":"0.01", + "shortName": "DSDRelay", + "version": "0.01", "description": "Control BLE relay board from the watch", + "icon": "icons8-relay-48.png", "tags": "ble,bluetooth", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"}, {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true} ] }, - { "id": "mandel", + { + "id": "mandel", "name": "Mandelbrot", - "shortName":"Mandel", - "icon": "mandel.png", - "version":"0.01", + "shortName": "Mandel", + "version": "0.01", "description": "Draw a zoomable Mandelbrot set", + "icon": "mandel.png", "tags": "game", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"mandel.app.js","url":"mandel.min.js"}, @@ -2620,997 +2913,1129 @@ { "id": "petrock", "name": "Pet rock", - "icon": "petrock.png", "version": "0.02", "description": "A virtual pet rock with wobbly eyes", - "tags": "game", + "icon": "petrock.png", "type": "app", + "tags": "game", + "supports": ["BANGLEJS"], "storage": [ - {"name": "petrock.app.js", "url": "app.js"}, - {"name": "petrock.img", "url": "app-icon.js", "evaluate": true} + {"name":"petrock.app.js","url":"app.js"}, + {"name":"petrock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "smartibot", + { + "id": "smartibot", "name": "Smartibot controller", - "shortName":"Smartibot", - "icon": "app.png", - "version":"0.01", + "shortName": "Smartibot", + "version": "0.01", "description": "Control a [Smartibot Robot](https://thecraftyrobot.net/) straight from your Bangle.js", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "storage": [ {"name":"smartibot.app.js","url":"app.js"}, {"name":"smartibot.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widncr", + { + "id": "widncr", "name": "NCR Logo Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Show the NodeConf Remote logo in the top left", + "icon": "widget.png", + "type": "widget", "tags": "widget", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widncr.wid.js","url":"widget.js"} ] }, - { "id": "ncrclk", + { + "id": "ncrclk", "name": "NCR Clock", - "shortName":"NCR Clock", - "icon": "app.png", - "version":"0.02", + "shortName": "NCR Clock", + "version": "0.02", "description": "NodeConf Remote clock", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"ncrclk.app.js","url":"app.js"}, {"name":"ncrclk.img","url":"app-icon.js","evaluate":true} ] }, -{ "id": "isoclock", - "name": "ISO Compliant Clock Face", - "shortName":"ISO Clock", - "icon": "isoclock.png", - "version":"0.02", - "description": "Tweaked fork of digiclock for ISO date and time", - "tags": "clock", - "type" : "clock", - "storage": [ - {"name":"isoclock.app.js","url":"isoclock.js"}, - {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} - ] -}, -{ "id": "gpstimeserver", - "name": "GPS Time Server", - "icon": "widget.png", - "version":"0.01", - "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", - "tags": "widget", - "type": "widget", - "readme": "README.md", - "storage": [ - {"name":"gpstimeserver.wid.js","url":"widget.js"} - ] -}, -{ "id": "tilthydro", - "name": "Tilt Hydrometer Display", - "shortName":"Tilt Hydro", - "icon": "app.png", - "version":"0.01", - "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)", - "tags": "tools,bluetooth", - "storage": [ - {"name":"tilthydro.app.js","url":"app.js"}, - {"name":"tilthydro.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "supmariodark", - "name": "Super mario clock night mode", - "shortName":"supmariodark", - "icon": "supmariodark.png", - "version":"0.01", - "description": "Super mario clock in night mode", - "tags": "clock", - "type" : "clock", - "storage": [ - {"name":"supmariodark.app.js","url":"supmariodark.js"}, - {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true}, - {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"}, - {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"}, - {"name":"banner-up.img","url":"banner-up.js","evaluate":true}, - {"name":"banner-down.img","url":"banner-down.js","evaluate":true}, - {"name":"brick2.img","url":"brick2.js","evaluate":true}, - {"name":"enemy.img","url":"enemy.js","evaluate":true}, - {"name":"flower.img","url":"flower.js","evaluate":true}, - {"name":"flower_b.img","url":"flower_b.js","evaluate":true}, - {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true}, - {"name":"pipe.img","url":"pipe.js","evaluate":true} - ] -}, -{ "id": "gmeter", - "name": "G-Meter", - "shortName":"G-Meter", - "icon": "app.png", - "version":"0.01", - "description": "Simple G-Meter", - "tags": "", - "storage": [ - {"name":"gmeter.app.js","url":"app.js"}, - {"name":"gmeter.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "dtlaunch", - "name": "Desktop Launcher", - "icon": "icon.png", - "version":"0.04", - "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", - "readme": "README.md", - "tags": "tool,system,launcher", - "type":"launch", - "storage": [ - {"name":"dtlaunch.app.js","url":"app.js"}, - {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "HRV", - "name": "Heart Rate Variability monitor", - "shortName":"HRV monitor", - "icon": "hrv.png", - "version":"0.04", - "description": "Heart Rate Variability monitor, see Readme for more info", - "tags": "", - "readme": "README.md", - "storage": [ - {"name":"HRV.app.js","url":"app.js"}, - {"name":"HRV.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "hardalarm", - "name": "Hard Alarm", - "shortName":"HardAlarm", - "icon": "app.png", - "version":"0.02", - "description": "Make sure you wake up! Count to the right number to turn off the alarm", - "tags": "tool,alarm,widget", - "storage": [ - {"name":"hardalarm.app.js","url":"app.js"}, - {"name":"hardalarm.boot.js","url":"boot.js"}, - {"name":"hardalarm.js","url":"hardalarm.js"}, - {"name":"hardalarm.img","url":"app-icon.js","evaluate":true}, - {"name":"hardalarm.wid.js","url":"widget.js"} - ], - "data": [ - {"name":"hardalarm.json"} - ] -}, -{ "id": "edisonsball", - "name": "Edison's Ball", - "shortName":"Edison's Ball", - "icon": "app-icon.png", - "version":"0.01", - "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", - "tags": "", - "readme": "README.md", - "storage": [ - {"name":"edisonsball.app.js","url":"app.js"}, - {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "hrrawexp", - "name": "HRM Data Exporter", - "shortName":"HRM Data Exporter", - "icon": "app-icon.png", - "version":"0.01", - "description": "export raw hrm signal data to a csv file", - "tags": "", - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"hrrawexp.app.js","url":"app.js"}, - {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "breath", - "name": "Breathing App", - "shortName":"Breathing App", - "icon": "app-icon.png", - "version":"0.01", - "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", - "tags": "tools,health", - "readme": "README.md", - "storage": [ - {"name":"breath.app.js","url":"app.js"}, - {"name":"breath.img","url":"app-icon.js","evaluate":true} - ], - "data": [ - {"name":"breath.settings.json","url":"settings.json"} - ] -}, -{ "id": "lazyclock", - "name": "Lazy Clock", - "icon": "lazyclock.png", - "version":"0.03", - "readme": "README.md", - "description": "Tells the time, roughly", - "tags": "clock", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, - {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} - ] -}, -{ "id": "astral", - "name": "Astral Clock", - "icon": "app-icon.png", - "version":"0.03", - "readme": "README.md", - "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", - "tags": "clock", - "type":"clock", - "storage": [ - {"name":"astral.app.js","url":"app.js"}, - {"name":"astral.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "alpinenav", - "name": "Alpine Nav", - "icon": "app-icon.png", - "version":"0.01", - "readme": "README.md", - "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", - "tags": "outdoors,gps", - "storage": [ - {"name":"alpinenav.app.js","url":"app.js"}, - {"name":"alpinenav.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "lifeclk", - "name": "Game of Life Clock", - "shortName":"Conway's Clock", - "icon": "app.png", - "version":"0.06", - "description": "Modification and clockification of Conway's Game of Life", - "tags": "clock", - "type" : "clock", - "readme": "README.md", - "storage": [ - {"name":"lifeclk.app.js","url":"app.min.js"}, - {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "speedalt", - "name": "GPS Adventure Sports", - "shortName":"GPS Adv Sport", - "icon": "app.png", - "version":"1.02", - "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", - "tags": "tool,outdoors", - "type":"app", - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"speedalt.app.js","url":"app.js"}, - {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, - {"name":"speedalt.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"speedalt.json"} + { + "id": "isoclock", + "name": "ISO Compliant Clock Face", + "shortName": "ISO Clock", + "version": "0.02", + "description": "Tweaked fork of digiclock for ISO date and time", + "icon": "isoclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"isoclock.app.js","url":"isoclock.js"}, + {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} ] -}, -{ "id": "de-stress", - "name": "De-Stress", - "shortName":"De-Stress", - "icon": "app.png", - "version":"0.02", - "description": "Simple haptic heartbeat", - "storage": [ - {"name":"de-stress.app.js","url":"app.js"}, - {"name":"de-stress.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "mclockplus", - "name": "Morph Clock+", - "shortName":"Morph Clock+", - "icon": "mclockplus.png", - "version":"0.02", - "description": "Morphing Clock with more readable seconds and date and additional stopwatch", - "tags": "clock", - "type": "clock", - "readme": "README.md", - "storage": [ - {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, - {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} - ] -}, -{ "id": "intervals", - "name": "Intervals App", - "shortName":"Intervals", - "icon": "intervals.png", - "version":"0.01", - "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", - "tags": "", - "storage": [ - {"name":"intervals.app.js","url":"intervals.app.js"}, - {"name":"intervals.img","url":"intervals-icon.js","evaluate":true} - ] -}, -{ "id": "planetarium", - "name": "Planetarium", - "shortName":"Planetarium", - "icon": "planetarium.png", - "readme": "README.md", - "version":"0.03", - "description": "Planetarium showing up to 500 stars using the watch location and time", - "tags": "", - "storage": [ - {"name":"planetarium.app.js","url":"planetarium.app.js"}, - {"name":"planetarium.data.csv","url":"planetarium.data.csv"}, - {"name":"planetarium.const.csv","url":"planetarium.const.csv"}, - {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"}, - {"name":"planetarium.settings.js","url":"settings.js"}, - {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true} - ], - "data":[ - {"name":"planetarium.json"} - ] -}, -{ "id": "tapelauncher", - "name": "Tape Launcher", - "icon": "icon.png", - "version":"0.02", - "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons", - "readme": "README.md", - "tags": "tool,system,launcher", - "type":"launch", - "storage": [ - {"name":"tapelauncher.app.js","url":"app.js"}, - {"name":"tapelauncher.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "oblique", - "name": "Oblique Strategies", - "icon": "eno.png", - "version": "0.01", - "description": "Oblique Strategies for creativity. Copied from Brian Eno.", - "tags": "tool", - "storage": [ - {"name":"oblique.app.js","url":"app.js"}, - {"name":"oblique.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "testuserinput", - "name": "Test User Input", - "shortName":"Test User Input", - "icon": "app.png", - "version":"0.06", - "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.", - "readme": "README.md", - "tags": "input,interface,buttons,touch,UI", - "storage": [ - {"name":"testuserinput.app.js","url":"app.js"}, - {"name":"testuserinput.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "gpssetup", - "name": "GPS Setup", - "shortName":"GPS Setup", - "icon": "gpssetup.png", - "version":"0.02", - "description": "Configure the GPS power options and store them in the GPS nvram", - "tags": "gps,tools,outdoors,bno2", - "readme": "README.md", - "storage": [ - {"name":"gpssetup","url":"gpssetup.js"}, - {"name":"gpssetup.settings.js","url":"settings.js"}, - {"name":"gpssetup.app.js","url":"app.js"}, - {"name":"gpssetup.img","url":"icon.js","evaluate":true} - ], - "data": [ - {"name":"gpssetup.settings.json","url":"settings.json"} - ] -}, -{ "id": "walkersclock", - "name": "Walkers Clock", - "shortName":"Walkers Clock", - "icon": "walkersclock48.png", - "version":"0.04", - "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference", - "type":"clock", - "tags": "clock, gps, tools, outdoors", - "readme": "README.md", - "storage": [ - {"name":"walkersclock.app.js","url":"app.js"}, - {"name":"walkersclock.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "widgps", - "name": "GPS Widget", - "icon": "widget.png", - "version":"0.02", - "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", - "tags": "widget,gps", - "type":"widget", - "readme": "README.md", - "storage": [ - {"name":"widgps.wid.js","url":"widget.js"} - ] -}, -{ "id": "widhrt", - "name": "HRM Widget", - "icon": "widget.png", - "version":"0.02", - "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", - "tags": "widget, hrm", - "type":"widget", - "readme": "README.md", - "storage": [ - {"name":"widhrt.wid.js","url":"widget.js"} - ] -}, -{ "id": "countdowntimer", - "name" : "Countdown Timer", - "icon": "countdowntimer.png", - "version": "0.01", - "description": "A simple countdown timer with a focus on usability", - "tags": "timer, tool", - "readme": "README.md", - "storage": [ - {"name": "countdowntimer.app.js", "url": "countdowntimer.js"}, - {"name": "countdowntimer.img", "url": "countdowntimer-icon.js", "evaluate": true} - ] -}, -{ "id": "helloworld", - "name": "hello, world!", - "shortName":"hello world", - "icon": "app.png", - "version":"0.02", - "description": "A cross cultural hello world!/hola mundo! app with colors and languages", - "readme": "README.md", - "tags": "input,interface,buttons,touch", - "storage": [ - {"name":"helloworld.app.js","url":"app.js"}, - {"name":"helloworld.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "widcom", - "name": "Compass Widget", - "icon": "widget.png", - "version":"0.01", - "description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later", - "tags": "widget, compass", - "type":"widget", - "readme": "README.md", - "storage": [ - {"name":"widcom.wid.js","url":"widget.js"} - ] -}, -{ "id": "arrow", - "name": "Arrow Compass", - "icon": "arrow.png", - "type":"app", - "version":"0.04", - "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", - "tags": "tool,outdoors", - "readme": "README.md", - "storage": [ - {"name":"arrow.app.js","url":"app.js"}, - {"name":"arrow.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "waypointer", - "name": "Way Pointer", - "icon": "waypointer.png", - "version":"0.01", - "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation", - "tags": "tool,outdoors,gps", - "readme": "README.md", - "interface":"waypoints.html", - "storage": [ - {"name":"waypointer.app.js","url":"app.js"}, - {"name":"waypointer.img","url":"icon.js","evaluate":true} - ], - "data": [ - {"name":"waypoints.json","url":"waypoints.json"} - ] -}, -{ "id": "color_catalog", - "name": "Colors Catalog", - "shortName":"Colors Catalog", - "icon": "app.png", - "version":"0.01", - "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", - "readme": "README.md", - "tags": "Color,input,buttons,touch,UI,bno2", - "storage": [ - {"name":"color_catalog.app.js","url":"app.js"}, - {"name":"color_catalog.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "UI4swatch", - "name": "UI 4 swatch", - "shortName":"UI 4 swatch", - "icon": "app.png", - "version":"0.01", - "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", - "readme": "README.md", - "tags": "Color, input,buttons,touch,UI", - "storage": [ - {"name":"UI4swatch.app.js","url":"app.js"}, - {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "simplest", - "name": "Simplest Clock", - "icon": "simplest.png", - "version":"0.02", - "description": "The simplest working clock, acts as a tutorial piece", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"simplest.app.js","url":"app.js"}, - {"name":"simplest.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "stepo", - "name": "Stepometer Clock", - "icon": "stepo.png", - "version":"0.03", - "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"stepo.app.js","url":"app.js"}, - {"name":"stepo.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "gbmusic", - "name": "Gadgetbridge Music Controls", - "shortName":"Music Controls", - "icon": "icon.png", - "version":"0.05", - "description": "Control the music on your Gadgetbridge-connected phone", - "tags": "tools,bluetooth,gadgetbridge,music", - "type":"app", - "allow_emulator": false, - "readme": "README.md", - "storage": [ - {"name":"gbmusic.app.js","url":"app.js"}, - {"name":"gbmusic.settings.js","url":"settings.js"}, - {"name":"gbmusic.wid.js","url":"widget.js"}, - {"name":"gbmusic.img","url":"icon.js","evaluate":true} - ], - "data": [ - {"name":"gbmusic.json"}, - {"name":"gbmusic.load.json"} - ] -}, -{ - "id": "battleship", - "name":"Battleship", - "icon":"battleship-icon.png", - "version": "0.01", - "readme": "README.md", - "description": "The classic game of battleship", - "tags": "game", - "allow_emulator": true, - "storage": [ - { - "name": "battleship.app.js", - "url": "battleship.js" - }, - { - "name": "battleship.img", - "url": "battleship-icon.js", - "evaluate": true - } - ] -}, -{ "id": "kitchen", - "name": "Kitchen Combo", - "icon": "kitchen.png", - "version":"0.13", - "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later", - "tags": "tool,outdoors,gps", - "type":"clock", - "readme": "README.md", - "interface":"waypoints.html", - "storage": [ - {"name":"kitchen.app.js","url":"kitchen.app.js"}, - {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, - {"name":"swatch.kit.js","url":"swatch.kit.js"}, - {"name":"gps.kit.js","url":"gps.kit.js"}, - {"name":"compass.kit.js","url":"compass.kit.js"}, - {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} - ], - "data": [ - {"name":"waypoints.json","url":"waypoints.json"} - ] -}, -{ "id": "banglebridge", - "name": "BangleBridge", - "shortName":"BangleBridge", + }, + { + "id": "gpstimeserver", + "name": "GPS Time Server", + "version": "0.01", + "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", "icon": "widget.png", - "version":"0.01", - "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", - "tags": "widget", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"gpstimeserver.wid.js","url":"widget.js"} + ] + }, + { + "id": "tilthydro", + "name": "Tilt Hydrometer Display", + "shortName": "Tilt Hydro", + "version": "0.01", + "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)", + "icon": "app.png", + "tags": "tools,bluetooth", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"tilthydro.app.js","url":"app.js"}, + {"name":"tilthydro.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "supmariodark", + "name": "Super mario clock night mode", + "shortName": "supmariodark", + "version": "0.01", + "description": "Super mario clock in night mode", + "icon": "supmariodark.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"supmariodark.app.js","url":"supmariodark.js"}, + {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true}, + {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"}, + {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"}, + {"name":"banner-up.img","url":"banner-up.js","evaluate":true}, + {"name":"banner-down.img","url":"banner-down.js","evaluate":true}, + {"name":"brick2.img","url":"brick2.js","evaluate":true}, + {"name":"enemy.img","url":"enemy.js","evaluate":true}, + {"name":"flower.img","url":"flower.js","evaluate":true}, + {"name":"flower_b.img","url":"flower_b.js","evaluate":true}, + {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true}, + {"name":"pipe.img","url":"pipe.js","evaluate":true} + ] + }, + { + "id": "gmeter", + "name": "G-Meter", + "shortName": "G-Meter", + "version": "0.01", + "description": "Simple G-Meter", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"gmeter.app.js","url":"app.js"}, + {"name":"gmeter.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "dtlaunch", + "name": "Desktop Launcher", + "version": "0.04", + "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", + "icon": "icon.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"dtlaunch.app.js","url":"app.js"}, + {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "HRV", + "name": "Heart Rate Variability monitor", + "shortName": "HRV monitor", + "version": "0.04", + "description": "Heart Rate Variability monitor, see Readme for more info", + "icon": "hrv.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"HRV.app.js","url":"app.js"}, + {"name":"HRV.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "hardalarm", + "name": "Hard Alarm", + "shortName": "HardAlarm", + "version": "0.02", + "description": "Make sure you wake up! Count to the right number to turn off the alarm", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"hardalarm.app.js","url":"app.js"}, + {"name":"hardalarm.boot.js","url":"boot.js"}, + {"name":"hardalarm.js","url":"hardalarm.js"}, + {"name":"hardalarm.img","url":"app-icon.js","evaluate":true}, + {"name":"hardalarm.wid.js","url":"widget.js"} + ], + "data": [{"name":"hardalarm.json"}] + }, + { + "id": "edisonsball", + "name": "Edison's Ball", + "shortName": "Edison's Ball", + "version": "0.01", + "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", + "icon": "app-icon.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"edisonsball.app.js","url":"app.js"}, + {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "hrrawexp", + "name": "HRM Data Exporter", + "shortName": "HRM Data Exporter", + "version": "0.01", + "description": "export raw hrm signal data to a csv file", + "icon": "app-icon.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"hrrawexp.app.js","url":"app.js"}, + {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "breath", + "name": "Breathing App", + "shortName": "Breathing App", + "version": "0.01", + "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", + "icon": "app-icon.png", + "tags": "tools,health", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"breath.app.js","url":"app.js"}, + {"name":"breath.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"breath.settings.json","url":"settings.json"}] + }, + { + "id": "lazyclock", + "name": "Lazy Clock", + "version": "0.03", + "description": "Tells the time, roughly", + "icon": "lazyclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, + {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} + ] + }, + { + "id": "astral", + "name": "Astral Clock", + "version": "0.03", + "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"astral.app.js","url":"app.js"}, + {"name":"astral.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "alpinenav", + "name": "Alpine Nav", + "version": "0.01", + "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", + "icon": "app-icon.png", + "tags": "outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"alpinenav.app.js","url":"app.js"}, + {"name":"alpinenav.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "lifeclk", + "name": "Game of Life Clock", + "shortName": "Conway's Clock", + "version": "0.06", + "description": "Modification and clockification of Conway's Game of Life", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"lifeclk.app.js","url":"app.min.js"}, + {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "speedalt", + "name": "GPS Adventure Sports", + "shortName": "GPS Adv Sport", + "version": "1.02", + "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "icon": "app.png", + "type": "app", + "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"speedalt.app.js","url":"app.js"}, + {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt.settings.js","url":"settings.js"} + ], + "data": [{"name":"speedalt.json"}] + }, + { + "id": "de-stress", + "name": "De-Stress", + "shortName": "De-Stress", + "version": "0.02", + "description": "Simple haptic heartbeat", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"de-stress.app.js","url":"app.js"}, + {"name":"de-stress.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "mclockplus", + "name": "Morph Clock+", + "shortName": "Morph Clock+", + "version": "0.02", + "description": "Morphing Clock with more readable seconds and date and additional stopwatch", + "icon": "mclockplus.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, + {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} + ] + }, + { + "id": "intervals", + "name": "Intervals App", + "shortName": "Intervals", + "version": "0.01", + "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", + "icon": "intervals.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"intervals.app.js","url":"intervals.app.js"}, + {"name":"intervals.img","url":"intervals-icon.js","evaluate":true} + ] + }, + { + "id": "planetarium", + "name": "Planetarium", + "shortName": "Planetarium", + "version": "0.03", + "description": "Planetarium showing up to 500 stars using the watch location and time", + "icon": "planetarium.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"planetarium.app.js","url":"planetarium.app.js"}, + {"name":"planetarium.data.csv","url":"planetarium.data.csv"}, + {"name":"planetarium.const.csv","url":"planetarium.const.csv"}, + {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"}, + {"name":"planetarium.settings.js","url":"settings.js"}, + {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true} + ], + "data": [{"name":"planetarium.json"}] + }, + { + "id": "tapelauncher", + "name": "Tape Launcher", + "version": "0.02", + "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons", + "icon": "icon.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"tapelauncher.app.js","url":"app.js"}, + {"name":"tapelauncher.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "oblique", + "name": "Oblique Strategies", + "version": "0.01", + "description": "Oblique Strategies for creativity. Copied from Brian Eno.", + "icon": "eno.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"oblique.app.js","url":"app.js"}, + {"name":"oblique.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "testuserinput", + "name": "Test User Input", + "shortName": "Test User Input", + "version": "0.06", + "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.", + "icon": "app.png", + "tags": "input,interface,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"testuserinput.app.js","url":"app.js"}, + {"name":"testuserinput.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "gpssetup", + "name": "GPS Setup", + "shortName": "GPS Setup", + "version": "0.02", + "description": "Configure the GPS power options and store them in the GPS nvram", + "icon": "gpssetup.png", + "tags": "gps,tools,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"gpssetup","url":"gpssetup.js"}, + {"name":"gpssetup.settings.js","url":"settings.js"}, + {"name":"gpssetup.app.js","url":"app.js"}, + {"name":"gpssetup.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"gpssetup.settings.json","url":"settings.json"}] + }, + { + "id": "walkersclock", + "name": "Walkers Clock", + "shortName": "Walkers Clock", + "version": "0.04", + "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference", + "icon": "walkersclock48.png", + "type": "clock", + "tags": "clock,gps,tools,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"walkersclock.app.js","url":"app.js"}, + {"name":"walkersclock.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "widgps", + "name": "GPS Widget", + "version": "0.02", + "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", + "icon": "widget.png", + "type": "widget", + "tags": "widget,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"widgps.wid.js","url":"widget.js"} + ] + }, + { + "id": "widhrt", + "name": "HRM Widget", + "version": "0.03", + "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", + "icon": "widget.png", + "type": "widget", + "tags": "widget,hrm", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widhrt.wid.js","url":"widget.js"} + ] + }, + { + "id": "countdowntimer", + "name": "Countdown Timer", + "version": "0.01", + "description": "A simple countdown timer with a focus on usability", + "icon": "countdowntimer.png", + "tags": "timer,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"countdowntimer.app.js","url":"countdowntimer.js"}, + {"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true} + ] + }, + { + "id": "helloworld", + "name": "hello, world!", + "shortName": "hello world", + "version": "0.02", + "description": "A cross cultural hello world!/hola mundo! app with colors and languages", + "icon": "app.png", + "tags": "input,interface,buttons,touch", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"helloworld.app.js","url":"app.js"}, + {"name":"helloworld.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "widcom", + "name": "Compass Widget", + "version": "0.01", + "description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later", + "icon": "widget.png", + "type": "widget", + "tags": "widget,compass", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"widcom.wid.js","url":"widget.js"} + ] + }, + { + "id": "arrow", + "name": "Arrow Compass", + "version": "0.05", + "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", + "icon": "arrow.png", + "type": "app", + "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"arrow.app.js","url":"app.js"}, + {"name":"arrow.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "waypointer", + "name": "Way Pointer", + "version": "0.01", + "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation", + "icon": "waypointer.png", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "waypoints.html", + "storage": [ + {"name":"waypointer.app.js","url":"app.js"}, + {"name":"waypointer.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"waypoints.json","url":"waypoints.json"}] + }, + { + "id": "color_catalog", + "name": "Colors Catalog", + "shortName": "Colors Catalog", + "version": "0.01", + "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", + "icon": "app.png", + "tags": "Color,input,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"color_catalog.app.js","url":"app.js"}, + {"name":"color_catalog.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "UI4swatch", + "name": "UI 4 swatch", + "shortName": "UI 4 swatch", + "version": "0.01", + "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", + "icon": "app.png", + "tags": "Color,input,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"UI4swatch.app.js","url":"app.js"}, + {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "simplest", + "name": "Simplest Clock", + "version": "0.03", + "description": "The simplest working clock, acts as a tutorial piece", + "icon": "simplest.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"simplest.app.js","url":"app.js"}, + {"name":"simplest.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "stepo", + "name": "Stepometer Clock", + "version": "0.03", + "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed", + "icon": "stepo.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"stepo.app.js","url":"app.js"}, + {"name":"stepo.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "gbmusic", + "name": "Gadgetbridge Music Controls", + "shortName": "Music Controls", + "version": "0.05", + "description": "Control the music on your Gadgetbridge-connected phone", + "icon": "icon.png", + "type": "app", + "tags": "tools,bluetooth,gadgetbridge,music", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"gbmusic.app.js","url":"app.js"}, + {"name":"gbmusic.settings.js","url":"settings.js"}, + {"name":"gbmusic.wid.js","url":"widget.js"}, + {"name":"gbmusic.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}] + }, + { + "id": "battleship", + "name": "Battleship", + "version": "0.01", + "description": "The classic game of battleship", + "icon": "battleship-icon.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"battleship.app.js","url":"battleship.js"}, + {"name":"battleship.img","url":"battleship-icon.js","evaluate":true} + ] + }, + { + "id": "kitchen", + "name": "Kitchen Combo", + "version": "0.13", + "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later", + "icon": "kitchen.png", + "type": "clock", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "waypoints.html", + "storage": [ + {"name":"kitchen.app.js","url":"kitchen.app.js"}, + {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, + {"name":"swatch.kit.js","url":"swatch.kit.js"}, + {"name":"gps.kit.js","url":"gps.kit.js"}, + {"name":"compass.kit.js","url":"compass.kit.js"}, + {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} + ], + "data": [{"name":"waypoints.json","url":"waypoints.json"}] + }, + { + "id": "banglebridge", + "name": "BangleBridge", + "shortName": "BangleBridge", + "version": "0.01", + "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"banglebridge.wid.js","url":"widget.js"}, - {"name":"banglebridge.watch.img","url":"watch.img"}, - {"name":"banglebridge.heart.img","url":"heart.img"} + {"name":"banglebridge.watch.img","url":"watch.img"}, + {"name":"banglebridge.heart.img","url":"heart.img"} ] - }, -{ "id": "qmsched", - "name": "Quiet Mode Schedule and Widget", - "shortName":"Quiet Mode", - "icon": "app.png", - "version":"0.02", - "description": "Automatically turn Quiet Mode on or off at set times", - "readme": "README.md", - "tags": "tool,widget", - "storage": [ - {"name":"qmsched","url":"lib.js"}, - {"name":"qmsched.app.js","url":"app.js"}, - {"name":"qmsched.boot.js","url":"boot.js"}, - {"name":"qmsched.img","url":"icon.js","evaluate":true}, - {"name":"qmsched.wid.js","url":"widget.js"} - ], - "data": [ - {"name":"qmsched.json"} - ] -}, -{ - "id": "hourstrike", - "name": "Hour Strike", - "shortName": "Hour Strike", - "icon": "app-icon.png", - "version": "0.08", - "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", - "tags": "tool,alarm", - "readme": "README.md", - "storage": [ - {"name":"hourstrike.app.js","url":"app.js"}, - {"name":"hourstrike.boot.js","url":"boot.js"}, - {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, - {"name":"hourstrike.json","url":"hourstrike.json"} - ] -}, -{ "id": "whereworld", - "name": "Where in the World?", - "shortName" : "Where World", - "icon": "app.png", - "version": "0.01", - "description": "Shows your current location on the world map", - "tags": "gps", - "storage": [ - {"name":"whereworld.app.js","url":"app.js"}, - {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, - {"name":"whereworld.worldmap","url":"worldmap"} - ] -}, -{ - "id": "omnitrix", - "name":"Omnitrix", - "icon":"omnitrix.png", - "version": "0.01", - "readme": "README.md", - "description": "An Omnitrix Showpiece", - "tags": "game", - "storage": [ - {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, - {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} - ] -}, -{ "id": "batclock", - "name": "Bat Clock", - "shortName":"Bat Clock", - "icon": "bat-clock.png", - "version":"0.02", - "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", - "tags": "clock", - "type": "clock", - "readme": "README.md", - "storage": [ - {"name":"batclock.app.js","url":"bat-clock.app.js"}, - {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} - ] -}, -{ "id":"doztime", - "name":"Dozenal Time", - "shortName":"Dozenal Time", - "icon":"app.png", - "version":"0.04", - "description":"A dozenal Holocene calendar and dozenal diurnal clock", - "tags":"clock", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"doztime.app.js","url":"app.js"}, - {"name":"doztime.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id":"gbtwist", - "name":"Gadgetbridge Twist Control", - "shortName":"Twist Control", - "icon":"app.png", - "version":"0.01", - "description":"Shake your wrist to control your music app via Gadgetbridge", - "tags":"tools,bluetooth,gadgetbridge,music", - "type":"app", - "allow_emulator":false, - "readme": "README.md", - "storage": [ - {"name":"gbtwist.app.js","url":"app.js"}, - {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "thermom", - "name": "Thermometer", - "icon": "app.png", - "version":"0.02", - "description": "Displays the current temperature, updated every 20 seconds", - "tags": "tool", - "allow_emulator":true, - "storage": [ - {"name":"thermom.app.js","url":"app.js"}, - {"name":"thermom.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "mysticdock", - "name": "Mystic Dock", - "icon": "mystic-dock.png", - "version":"1.00", - "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", - "tags": "dock", - "type":"dock", - "readme": "README.md", - "storage": [ - {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, - {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, - {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, - {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} - ] -}, -{ "id": "mysticclock", - "name": "Mystic Clock", - "icon": "mystic-clock.png", - "version":"1.01", - "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, - {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, - {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} - ] -}, -{ "id": "hcclock", - "name": "Hi-Contrast Clock", - "icon": "hcclock-icon.png", - "version":"0.01", - "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", - "tags": "clock", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} - ] -}, -{ "id": "thermomF", - "name": "Fahrenheit Temp", - "icon": "thermf.png", - "version":"0.01", - "description": "A modification of the Thermometer App to display temprature in Fahrenheit", - "tags": "tool", - "storage": [ - {"name":"thermomF.app.js","url":"app.js"}, - {"name":"thermomF.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "nixie", - "name": "Nixie Clock", - "shortName":"Nixie", - "icon": "nixie.png", - "version":"0.01", - "description": "A nixie tube clock for both Bangle 1 and 2.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"nixie.app.js","url":"app.js"}, - {"name":"nixie.img","url":"app-icon.js","evaluate":true}, - {"name":"m_vatch.js","url":"m_vatch.js"} - ] -}, -{ "id": "carcrazy", - "name": "Car Crazy", - "shortName":"Car Crazy", - "icon": "carcrash.png", - "version":"0.03", - "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", - "tags": "game", - "readme": "README.md", - "storage": [ - {"name":"carcrazy.app.js","url":"app.js"}, - {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, - {"name":"carcrazy.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"app.json"} - ] -}, -{ "id": "shortcuts", - "name": "Shortcuts", - "shortName":"Shortcuts", - "icon": "app.png", - "version":"0.01", - "description": "Quickly load your favourite apps from (almost) any watch face.", - "tags": "tool", - "type": "bootloader", - "readme": "README.md", - "storage": [ - {"name":"shortcuts.boot.js","url":"boot.js"}, - {"name":"shortcuts.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"shortcuts.json"} - ] -}, -{ "id": "vectorclock", - "name": "Vector Clock", - "icon": "app.png", - "version": "0.02", - "description": "A digital clock that uses the built-in vector font.", - "tags": "clock", - "type": "clock", - "allow_emulator": true, - "storage": [ - {"name":"vectorclock.app.js","url":"app.js"}, - {"name":"vectorclock.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "fd6fdetect", - "name": "fd6fdetect", - "shortName":"fd6fdetect", - "icon": "app.png", - "version":"0.1", - "description": "Allows you to see 0xFD6F beacons near you.", - "tags": "tool", - "storage": [ - {"name":"fd6fdetect.app.js","url":"app.js"}, - {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "choozi", - "name": "Choozi", - "icon": "app.png", - "version":"0.01", - "description": "Choose people or things at random using Bangle.js.", - "tags": "tool", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"choozi.app.js","url":"app.js"}, - {"name":"choozi.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "widclkbttm", - "name": "Digital clock (Bottom) widget", - "shortName":"Digital clock Bottom Widget", - "icon": "widclkbttm.png", - "version":"0.03", - "description": "Displays time in the bottom area.", - "readme": "README.md", - "tags": "widget", - "type": "widget", - "storage": [ - {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} - ] -}, -{ "id": "pastel", - "name": "Pastel Clock", - "shortName": "Pastel", - "icon": "pastel.png", - "version":"0.05", - "description": "A Configurable clock with custom fonts and background", - "tags": "clock,b2", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"pastel.app.js","url":"pastel.app.js"}, - {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, - {"name":"pastel.settings.js","url":"pastel.settings.js"} - ], - "data": [ - {"name":"pastel.json"} - ] -}, -{ "id": "antonclk", - "name": "Anton Clock", - "icon": "app.png", - "version":"0.02", - "description": "A simple clock using the bold Anton font.", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "waveclk", - "name": "Wave Clock", - "icon": "app.png", - "version":"0.02", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"waveclk.app.js","url":"app.js"}, - {"name":"waveclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "floralclk", - "name": "Floral Clock", - "icon": "app.png", - "version":"0.01", - "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"floralclk.app.js","url":"app.js"}, - {"name":"floralclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "score", - "name": "Score Tracker", - "icon": "score.app.png", - "version":"0.01", - "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", - "readme": "README.md", - "tags": "b2", - "type": "app", - "storage": [ - {"name":"score.app.js","url":"score.app.js"}, - {"name":"score.settings.js","url":"score.settings.js"}, - {"name":"score.presets.json","url":"score.presets.json"}, - {"name":"score.img","url":"score.app-icon.js","evaluate":true} - ], - "data": [ - {"name":"score.json"} - ] -}, -{ "id": "menusmall", - "name": "Small Menus", - "icon": "app.png", - "version":"0.01", - "description": "Replace Bangle.js 2's menus with a version that contains smaller text", - "tags": "b2,bno1,system", - "type": "boot", - "storage": [ - {"name":"menusmall.boot.js","url":"boot.js"} - ] -}, -{ "id": "ffcniftya", - "name": "Nifty-A Clock", - "icon": "app.png", - "version":"0.01", - "description": "A nifty clock with time and date", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"ffcniftya.app.js","url":"app.js"}, - {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} - ] -} + }, + { + "id": "qmsched", + "name": "Quiet Mode Schedule and Widget", + "shortName": "Quiet Mode", + "version": "0.02", + "description": "Automatically turn Quiet Mode on or off at set times", + "icon": "app.png", + "tags": "tool,widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"qmsched","url":"lib.js"}, + {"name":"qmsched.app.js","url":"app.js"}, + {"name":"qmsched.boot.js","url":"boot.js"}, + {"name":"qmsched.img","url":"icon.js","evaluate":true}, + {"name":"qmsched.wid.js","url":"widget.js"} + ], + "data": [{"name":"qmsched.json"}] + }, + { + "id": "hourstrike", + "name": "Hour Strike", + "shortName": "Hour Strike", + "version": "0.08", + "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", + "icon": "app-icon.png", + "tags": "tool,alarm", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"hourstrike.app.js","url":"app.js"}, + {"name":"hourstrike.boot.js","url":"boot.js"}, + {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, + {"name":"hourstrike.json","url":"hourstrike.json"} + ] + }, + { + "id": "whereworld", + "name": "Where in the World?", + "shortName": "Where World", + "version": "0.01", + "description": "Shows your current location on the world map", + "icon": "app.png", + "tags": "gps", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"whereworld.app.js","url":"app.js"}, + {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, + {"name":"whereworld.worldmap","url":"worldmap"} + ] + }, + { + "id": "omnitrix", + "name": "Omnitrix", + "version": "0.01", + "description": "An Omnitrix Showpiece", + "icon": "omnitrix.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, + {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} + ] + }, + { + "id": "batclock", + "name": "Bat Clock", + "shortName": "Bat Clock", + "version": "0.02", + "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", + "icon": "bat-clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"batclock.app.js","url":"bat-clock.app.js"}, + {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} + ] + }, + { + "id": "doztime", + "name": "Dozenal Time", + "shortName": "Dozenal Time", + "version": "0.04", + "description": "A dozenal Holocene calendar and dozenal diurnal clock", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"doztime.app.js","url":"app.js"}, + {"name":"doztime.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "gbtwist", + "name": "Gadgetbridge Twist Control", + "shortName": "Twist Control", + "version": "0.01", + "description": "Shake your wrist to control your music app via Gadgetbridge", + "icon": "app.png", + "type": "app", + "tags": "tools,bluetooth,gadgetbridge,music", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"gbtwist.app.js","url":"app.js"}, + {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "thermom", + "name": "Thermometer", + "version": "0.02", + "description": "Displays the current temperature, updated every 20 seconds", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"thermom.app.js","url":"app.js"}, + {"name":"thermom.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "mysticdock", + "name": "Mystic Dock", + "version": "1.00", + "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", + "icon": "mystic-dock.png", + "type": "dock", + "tags": "dock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, + {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, + {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, + {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} + ] + }, + { + "id": "mysticclock", + "name": "Mystic Clock", + "version": "1.01", + "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", + "icon": "mystic-clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, + {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, + {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} + ] + }, + { + "id": "hcclock", + "name": "Hi-Contrast Clock", + "version": "0.02", + "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", + "icon": "hcclock-icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"hcclock.app.js","url":"hcclock.app.js"}, + {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} + ] + }, + { + "id": "thermomF", + "name": "Fahrenheit Temp", + "version": "0.01", + "description": "A modification of the Thermometer App to display temprature in Fahrenheit", + "icon": "thermf.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"thermomF.app.js","url":"app.js"}, + {"name":"thermomF.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "nixie", + "name": "Nixie Clock", + "shortName": "Nixie", + "version": "0.01", + "description": "A nixie tube clock for both Bangle 1 and 2.", + "icon": "nixie.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"nixie.app.js","url":"app.js"}, + {"name":"nixie.img","url":"app-icon.js","evaluate":true}, + {"name":"m_vatch.js","url":"m_vatch.js"} + ] + }, + { + "id": "carcrazy", + "name": "Car Crazy", + "shortName": "Car Crazy", + "version": "0.03", + "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", + "icon": "carcrash.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"carcrazy.app.js","url":"app.js"}, + {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, + {"name":"carcrazy.settings.js","url":"settings.js"} + ], + "data": [{"name":"app.json"}] + }, + { + "id": "shortcuts", + "name": "Shortcuts", + "shortName": "Shortcuts", + "version": "0.01", + "description": "Quickly load your favourite apps from (almost) any watch face.", + "icon": "app.png", + "type": "bootloader", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"shortcuts.boot.js","url":"boot.js"}, + {"name":"shortcuts.settings.js","url":"settings.js"} + ], + "data": [{"name":"shortcuts.json"}] + }, + { + "id": "vectorclock", + "name": "Vector Clock", + "version": "0.02", + "description": "A digital clock that uses the built-in vector font.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"vectorclock.app.js","url":"app.js"}, + {"name":"vectorclock.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "fd6fdetect", + "name": "fd6fdetect", + "shortName": "fd6fdetect", + "version": "0.1", + "description": "Allows you to see 0xFD6F beacons near you.", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"fd6fdetect.app.js","url":"app.js"}, + {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "choozi", + "name": "Choozi", + "version": "0.01", + "description": "Choose people or things at random using Bangle.js.", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"choozi.app.js","url":"app.js"}, + {"name":"choozi.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "widclkbttm", + "name": "Digital clock (Bottom) widget", + "shortName": "Digital clock Bottom Widget", + "version": "0.03", + "description": "Displays time in the bottom area.", + "icon": "widclkbttm.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} + ] + }, + { + "id": "pastel", + "name": "Pastel Clock", + "shortName": "Pastel", + "version": "0.05", + "description": "A Configurable clock with custom fonts and background", + "icon": "pastel.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"pastel.app.js","url":"pastel.app.js"}, + {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, + {"name":"pastel.settings.js","url":"pastel.settings.js"} + ], + "data": [{"name":"pastel.json"}] + }, + { + "id": "antonclk", + "name": "Anton Clock", + "version": "0.02", + "description": "A simple clock using the bold Anton font.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"antonclk.app.js","url":"app.js"}, + {"name":"antonclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "waveclk", + "name": "Wave Clock", + "version": "0.02", + "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"waveclk.app.js","url":"app.js"}, + {"name":"waveclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "floralclk", + "name": "Floral Clock", + "version": "0.01", + "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"floralclk.app.js","url":"app.js"}, + {"name":"floralclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "score", + "name": "Score Tracker", + "version": "0.01", + "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", + "icon": "score.app.png", + "type": "app", + "tags": "", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"score.app.js","url":"score.app.js"}, + {"name":"score.settings.js","url":"score.settings.js"}, + {"name":"score.presets.json","url":"score.presets.json"}, + {"name":"score.img","url":"score.app-icon.js","evaluate":true} + ], + "data": [{"name":"score.json"}] + }, + { + "id": "menusmall", + "name": "Small Menus", + "version": "0.01", + "description": "Replace Bangle.js 2's menus with a version that contains smaller text", + "icon": "app.png", + "type": "boot", + "tags": "system", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"menusmall.boot.js","url":"boot.js"} + ] + }, + { + "id": "ffcniftya", + "name": "Nifty-A Clock", + "version": "0.01", + "description": "A nifty clock with time and date", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"ffcniftya.app.js","url":"app.js"}, + {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "stopwatch", + "name": "Stopwatch Touch", + "version": "0.01", + "description": "A touch based stop watch for Bangle JS 2", + "icon": "stopwatch.png", + "tags": "tools,app,b2", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, + {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} + ] + }, + { "id": "vernierrespirate", + "name": "Vernier Go Direct Respiration Belt", + "shortName":"Respiration Belt", + "version":"0.01", + "description": "Connects to a Go Direct Respiration Belt and shows respiration rate", + "icon": "app.png", + "tags": "health,bluetooth", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"vernierrespirate.app.js","url":"app.js"}, + {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"vernierrespirate.json"}] + } ] diff --git a/apps/_example_app/add_to_apps.json b/apps/_example_app/add_to_apps.json index 1585ab73d..cc28e1e93 100644 --- a/apps/_example_app/add_to_apps.json +++ b/apps/_example_app/add_to_apps.json @@ -2,13 +2,14 @@ { "id": "7chname", "name": "My app's human readable name", "shortName":"Short Name", - "icon": "app.png", "version":"0.01", "description": "A detailed description of my great app", + "icon": "app.png", "tags": "", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"7chname.app.js","url":"app.js"}, {"name":"7chname.img","url":"app-icon.js","evaluate":true} ] -} \ No newline at end of file +} diff --git a/apps/_example_widget/add_to_apps.json b/apps/_example_widget/add_to_apps.json index 527c698a0..b55adce9d 100644 --- a/apps/_example_widget/add_to_apps.json +++ b/apps/_example_widget/add_to_apps.json @@ -2,11 +2,12 @@ { "id": "7chname", "name": "My widget's human readable name", "shortName":"Short Name", - "icon": "widget.png", "version":"0.01", "description": "A detailed description of my great widget", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"7chname.wid.js","url":"widget.js"} diff --git a/apps/arrow/ChangeLog b/apps/arrow/ChangeLog index 2f1b2b4c4..edd5ccb3d 100644 --- a/apps/arrow/ChangeLog +++ b/apps/arrow/ChangeLog @@ -1,4 +1,6 @@ 0.01: First version 0.02: Moved arrow image load to global scope 0.03: faster drawCompass() function, does not cause buttons to become unresponsive -0.04: removed LCD1.write() as it was keeping LCD on +0.04: removed LED1.write() as it was keeping LCD on +0.05: Turn compass off when screen off + Calibrate at start if no info diff --git a/apps/arrow/app.js b/apps/arrow/app.js index 2cb3a42ad..f1f85e880 100644 --- a/apps/arrow/app.js +++ b/apps/arrow/app.js @@ -1,5 +1,5 @@ -var pal1color = new Uint16Array([0x0000,0xFFC0],0,1); -var pal2color = new Uint16Array([0x0000,0xffff],0,1); +var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1); +var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1); var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true}); var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true}); var intervalRef; @@ -7,6 +7,7 @@ var bearing=0; // always point north var heading = 0; var oldHeading = 0; var candraw = false; +var isCalibrating = false; var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; function flip1(x,y) { @@ -29,7 +30,7 @@ function drawCompass(hd) { if (Math.abs(hd - oldHeading) < 2) return 0; hd=hd*Math.PI/180; var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760]; - + // using polar cordinates, 64,64 is the offset from the 0,0 origin var poly = [ 64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]), @@ -40,16 +41,16 @@ function drawCompass(hd) { 64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]), 64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6]) ]; - + buf1.fillPoly(poly); flip1(56, 56); } // stops violent compass swings and wobbles, takes 3ms -function newHeading(m,h){ +function newHeading(m,h){ var s = Math.abs(m - h); var delta = (m>h)?1:-1; - if (s>=180){s=360-s; delta = -delta;} + if (s>=180){s=360-s; delta = -delta;} if (s<2) return h; var hd = h + delta*(1 + Math.round(s/5)); if (hd<0) hd+=360; @@ -76,7 +77,7 @@ function tiltfixread(O,S){ return psi; } -function reading() { +function reading(m) { var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale); heading = newHeading(d,heading); var dir = bearing - heading; @@ -97,18 +98,19 @@ function reading() { function calibrate(){ var max={x:-32000, y:-32000, z:-32000}, min={x:32000, y:32000, z:32000}; - var ref = setInterval(()=>{ - var m = Bangle.getCompass(); + function onMag(m) { max.x = m.x>max.x?m.x:max.x; max.y = m.y>max.y?m.y:max.y; max.z = m.z>max.z?m.z:max.z; min.x = m.x { setTimeout(()=>{ - if(ref) clearInterval(ref); + Bangle.removeListener('mag', onMag); var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2}; var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2}; var avg = (delta.x+delta.y+delta.z)/3; @@ -132,6 +134,7 @@ function docalibrate(e,first){ flip1(56,56); calibrate().then((r)=>{ + isCalibrating = false; require("Storage").write("magnav.json",r); Bangle.buzz(); CALIBDATA = r; @@ -142,27 +145,39 @@ function docalibrate(e,first){ startdraw(); setTimeout(setButtons,1000); } - } - if (first===undefined) first=false; + } + + if (first === undefined) first = false; + stopdraw(); clearWatch(); - if (first) + isCalibrating = true; + + if (first) E.showAlert(msg,title).then(action.bind(null,true)); - else + else E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action); } function startdraw(){ + Bangle.setCompassPower(1, "app"); + g.clear(); g.setColor(1,1,1); Bangle.drawWidgets(); candraw = true; - intervalRef = setInterval(reading,500); + if (intervalRef) clearInterval(intervalRef); + intervalRef = setInterval(reading,200); } function stopdraw() { candraw=false; - if(intervalRef) {clearInterval(intervalRef);} + + Bangle.setCompassPower(0, "app"); + if (intervalRef) { + clearInterval(intervalRef); + intervalRef = undefined; + } } function setButtons(){ @@ -170,8 +185,9 @@ function setButtons(){ setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"}); } - + Bangle.on('lcdPower',function(on) { + if (isCalibrating) return; if (on) { startdraw(); } else { @@ -179,9 +195,8 @@ Bangle.on('lcdPower',function(on) { } }); -Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); - Bangle.loadWidgets(); -Bangle.setCompassPower(1); -startdraw(); setButtons(); + +Bangle.setLCDPower(1); +if (CALIBDATA) startdraw(); else docalibrate({},true); diff --git a/apps/berlinc/ChangeLog b/apps/berlinc/ChangeLog index 65ed0505f..9e9c1a6aa 100644 --- a/apps/berlinc/ChangeLog +++ b/apps/berlinc/ChangeLog @@ -1,3 +1,6 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal. 0.04: Update to use Bangle.setUI instead of setWatch +0.05: Update *on* the minute rather than every 15 secs + Now show widgets + Make compatible with themes, and Bangle.js 2 diff --git a/apps/berlinc/berlin-clock.js b/apps/berlinc/berlin-clock.js index 144fa5ba7..0dd8ff8ee 100644 --- a/apps/berlinc/berlin-clock.js +++ b/apps/berlinc/berlin-clock.js @@ -1,7 +1,7 @@ // Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr // https://github.com/eska-muc/BangleApps const fields = [4, 4, 11, 4]; -const offset = 20; +const offset = 24; const width = g.getWidth() - 2 * offset; const height = g.getHeight() - 2 * offset; const rowHeight = height / 4; @@ -10,11 +10,23 @@ var show_date = false; var show_time = false; var yy = 0; -rowlights = []; -time_digit = []; +var rowlights = []; +var time_digit = []; -function drawBerlinClock() { - g.clear(); +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()); var now = new Date(); // show date below the clock @@ -24,8 +36,7 @@ function drawBerlinClock() { var day = now.getDate(); var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`; var strWidth = g.stringWidth(dateString); - g.setColor(1, 1, 1); - g.setFontAlign(-1,-1); + g.setColor(g.theme.fg).setFontAlign(-1,-1); g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4); } @@ -50,8 +61,7 @@ function drawBerlinClock() { x2 = (col + 1) * boxWidth + offset; y2 = (row + 1) * rowHeight + offset; - g.setColor(1, 1, 1); - g.drawRect(x1, y1, x2, y2); + g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2); if (col < rowlights[row]) { if (row === 2) { if (((col + 1) % 3) === 0) { @@ -65,46 +75,42 @@ function drawBerlinClock() { g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2); } if (row == 3 && show_time) { - g.setColor(1,1,1); - g.setFontAlign(0,0); + g.setColor(g.theme.fg).setFontAlign(0,0); g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2); } } } + + queueDraw(); } function toggleDate() { show_date = ! show_date; - drawBerlinClock(); + draw(); } function toggleTime() { show_time = ! show_time; - drawBerlinClock(); + draw(); } -// special function to handle display switch on -Bangle.on('lcdPower', (on) => { - g.clear(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - Bangle.drawWidgets(); - // call your app function here - drawBerlinClock(); + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// refesh every 15 sec -setInterval(drawBerlinClock, 15E3); +// Show launcher when button pressed, handle up/down +Bangle.setUI("clockupdown", dir=> { + if (dir<0) toggleTime(); + if (dir>0) toggleDate(); +}); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -drawBerlinClock(); -if (BTN3) { - // Toggle date display, when BTN3 is pressed - setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"}); - // Toggle date display, when BTN3 is pressed - setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"}); -} -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 6cdf1b0e5..59d9e4c65 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -31,3 +31,6 @@ Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js 0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now 0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics +0.32: Fix single quote error in g.wrapString polyfill + improve g.stringMetrics polyfill + Fix issue where re-running bootupdate could disable existing polyfills diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 7210ae731..269a80831 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -81,9 +81,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`; if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`; if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; // Pre-2v10 firmwares without a theme/setUI +delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.theme) { boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`; } +delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it boot += `Bangle.setUI=function(mode, cb) { if (Bangle.btnWatches) { @@ -131,6 +133,7 @@ else if (mode=="updown") { throw new Error("Unknown UI mode"); };\n`; } +delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.imageMetrics=function(src) { if (src[0]) return {width:src[0],height:src[1]}; @@ -141,15 +144,18 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi return {width:im.charCodeAt(0), height:im.charCodeAt(1)}; };\n`; } +delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.stringMetrics=function(txt) { - return {width:this.stringWidth(txt), height:this.getFontHeight()}; + txt = txt.toString().split("\\n"); + return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; };\n`; } +delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.wrapString=function(str, maxWidth) { var lines = []; - for (var unwrappedLine of str.split("\n")) { + for (var unwrappedLine of str.split("\\n")) { var words = unwrappedLine.split(" "); var line = words.shift(); for (var word of words) { diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/bthrm/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md new file mode 100644 index 000000000..f0c7775c2 --- /dev/null +++ b/apps/bthrm/README.md @@ -0,0 +1,45 @@ +# Bluetooth Heart Rate Monitor + +When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. + +HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor. + +This means it's compatible with many Bangle.js apps including: + +* [Heart Rate Widget](https://banglejs.com/apps/#widhrt) +* [Heart Rate Recorder](https://banglejs.com/apps/#heart) + +It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm) +as that requires live sensor data (rather than just BPM readings). + +## Usage + +Just install the app, then install an app that uses the heart rate monitor. + +Once installed it'll automatically try and connect to the first bluetooth +heart rate monitor it finds. + +**To disable this and return to normal HRM, uninstall the app** + +## Compatible Heart Rate Monitors + +This works with any heart rate monitor providing the standard Bluetooth +Heart Rate Service (`180D`) and characteristic (`2A37`). + +So far it has been tested on: + +* CooSpo Bluetooth Heart Rate Monitor + +## Internals + +This replaces `Bangle.setHRMPower` with its own implementation. + +## TODO + +* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off +* A widget to show connection state? +* Specify a specific device by address? + +## Creator + +Gordon Williams diff --git a/apps/bthrm/app-icon.js b/apps/bthrm/app-icon.js new file mode 100644 index 000000000..04a5ee610 --- /dev/null +++ b/apps/bthrm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA==")) diff --git a/apps/bthrm/app.png b/apps/bthrm/app.png new file mode 100644 index 000000000..40c2ab024 Binary files /dev/null and b/apps/bthrm/app.png differ diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js new file mode 100644 index 000000000..88e574480 --- /dev/null +++ b/apps/bthrm/boot.js @@ -0,0 +1,79 @@ +(function() { + var log = function() {};//print + var gatt; + var status; + + Bangle.isHRMOn = function() { + return (status=="searching" || status=="connecting") || (gatt!==undefined); + } + Bangle.setHRMPower = function(isOn, app) { + // Do app power handling + if (!app) app="?"; + log("setHRMPower ->", isOn, app); + if (Bangle._PWR===undefined) Bangle._PWR={}; + if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[]; + if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app); + if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app); + isOn = Bangle._PWR.HRM.length; + // so now we know if we're really on + if (isOn) { + log("setHRMPower on", app); + if (!Bangle.isHRMOn()) { + log("HRM not already on"); + status = "searching"; + NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { + log("Found device "+device.id); + status = "connecting"; + device.on('gattserverdisconnected', function(reason) { + gatt = undefined; + }); + return device.gatt.connect(); + }).then(function(g) { + log("Connected"); + gatt = g; + return gatt.getPrimaryService(0x180D); + }).then(function(service) { + return service.getCharacteristic(0x2A37); + }).then(function(characteristic) { + log("Got characteristic"); + characteristic.on('characteristicvaluechanged', function(event) { + var dv = event.target.value; + var flags = dv.getUint8(0); + // 0 = 8 or 16 bit + // 1,2 = sensor contact + // 3 = energy expended shown + // 4 = RR interval + var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit + /* var idx = 2 + (flags&1); // index of next field + if (flags&8) idx += 2; // energy expended + if (flags&16) { + var interval = dv.getUint16(idx,1); // in milliseconds + }*/ + Bangle.emit('HRM',{ + bpm:bpm, + confidence:100 + }); + }); + return characteristic.startNotifications(); + }).then(function() { + log("Ready"); + status = "ok"; + }).catch(function(err) { + log("Error",err); + gatt = undefined; + status = "error"; + }); + } + } else { // not on + log("setHRMPower off", app); + if (gatt) { + log("HRM connected - disconnecting"); + status = undefined; + try {gatt.disconnect();}catch(e) { + log("HRM disconnect error", e); + } + gatt = undefined; + } + } + }; +})(); diff --git a/apps/cliock/ChangeLog b/apps/cliock/ChangeLog index 66ef62eae..2a93a0d5f 100644 --- a/apps/cliock/ChangeLog +++ b/apps/cliock/ChangeLog @@ -5,3 +5,5 @@ 0.11: added Heart Rate Monitor status and ability to turn on/off 0.12: added support for different locales 0.13: Use setUI, work with smaller screens and themes +0.14: Fix BTN1 (fix #853) + Add light/dark theme support diff --git a/apps/cliock/app.js b/apps/cliock/app.js index 6853aaf6f..0fd6ea580 100644 --- a/apps/cliock/app.js +++ b/apps/cliock/app.js @@ -20,6 +20,8 @@ const HRT_FN_MODE = "fn_hrt"; let infoMode = NONE_MODE; let functionMode = NONE_FN_MODE; +let textCol = g.theme.dark ? "#0f0" : "#080"; + function drawAll(){ updateTime(); updateRest(new Date()); @@ -45,9 +47,7 @@ function writeLineStart(line){ function writeLine(str,line){ var y = marginTop+line*fontheight; g.setFont("6x8",fontsize); - //g.setColor(0,1,0); - g.setColor("#0f0"); - g.setFontAlign(-1,-1); + g.setColor(textCol).setFontAlign(-1,-1); g.clearRect(0,y,((str.length+1)*20),y+fontheight-1); writeLineStart(line); g.drawString(str,25,y); @@ -56,7 +56,7 @@ function writeLine(str,line){ function drawInfo(line) { let val; let str = ""; - let col = "#0f0"; // green + let col = textCol; // green //console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode); @@ -64,7 +64,7 @@ function drawInfo(line) { case NONE_FN_MODE: break; case HRT_FN_MODE: - col = "#0ff"; // cyan + col = g.theme.dark ? "#0ff": "#088"; // cyan str = "HRM: " + (hrtOn ? "ON" : "OFF"); drawModeLine(line,str,col); return; @@ -72,7 +72,7 @@ function drawInfo(line) { switch(infoMode) { case NONE_MODE: - col = "#fff"; + col = g.theme.bg; str = ""; break; case HRT_MODE: @@ -104,9 +104,8 @@ function drawModeLine(line, str, col) { g.setColor(col); var y = marginTop+line*fontheight; g.fillRect(0, y, 239, y+fontheight-1); - g.setColor(0); - g.setFontAlign(0, -1); - g.drawString(str, g.getWidth()/2, y); + g.setColor(g.theme.bg).setFontAlign(0, 0); + g.drawString(str, g.getWidth()/2, y+fontheight/2); } function changeInfoMode() { @@ -193,7 +192,7 @@ Bangle.on('lcdPower',function(on) { var click = setInterval(updateTime, 1000); // Show launcher when button pressed Bangle.setUI("clockupdown", btn=>{ - if (btn==0) changeInfoMode(); - if (btn==1) changeFunctionMode(); + if (btn<0) changeInfoMode(); + if (btn>0) changeFunctionMode(); drawAll(); -}); +}); \ No newline at end of file diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog index e70a5688b..d2bfbd4fa 100644 --- a/apps/compass/ChangeLog +++ b/apps/compass/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Show text if uncalibrated -0.03: Eliminate flickering \ No newline at end of file +0.03: Eliminate flickering +0.04: Fix for Bangle.js 2 and themes diff --git a/apps/compass/compass.js b/apps/compass/compass.js index 9b7ed56b7..d26081dd5 100644 --- a/apps/compass/compass.js +++ b/apps/compass/compass.js @@ -1,60 +1,72 @@ -var tg = Graphics.createArrayBuffer(120,20,1,{msb:true}); -var timg = { - width:tg.getWidth(), - height:tg.getHeight(), - bpp:1, - buffer:tg.buffer -}; - -var ag = Graphics.createArrayBuffer(160,160,2,{msb:true}); +var W = g.getWidth(); +var M = W/2; // middle of screen +// Angle buffer +var AGS = W > 200 ? 160 : 120; // buffer size +var AGM = AGS/2; // midpoint/radius +var AGH = AGM-10; // hand size +var ag = Graphics.createArrayBuffer(AGS,AGS,2,{msb:true}); var aimg = { width:ag.getWidth(), height:ag.getHeight(), bpp:2, buffer:ag.buffer, - palette:new Uint16Array([0,0x03FF,0xF800,0x001F]) + palette:new Uint16Array([ + g.theme.bg, + g.toColor("#07f"), + g.toColor("#f00"), + g.toColor("#00f")]) }; -ag.setColor(1); -ag.fillCircle(80,80,79,79); -ag.setColor(0); -ag.fillCircle(80,80,69,69); +ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1); +ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11); function arrow(r,c) { r=r*Math.PI/180; var p = Math.PI/2; - ag.setColor(c); - ag.fillPoly([ - 80+60*Math.sin(r), 80-60*Math.cos(r), - 80+10*Math.sin(r+p), 80-10*Math.cos(r+p), - 80+10*Math.sin(r-p), 80-10*Math.cos(r-p), + ag.setColor(c).fillPoly([ + AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r), + AGM+10*Math.sin(r+p), AGM-10*Math.cos(r+p), + AGM+10*Math.sin(r-p), AGM-10*Math.cos(r-p), ]); } +var wasUncalibrated = false; var oldHeading = 0; Bangle.on('mag', function(m) { if (!Bangle.isLCDOn()) return; - tg.clear(); - tg.setFont("6x8",1); - tg.setColor(1); + g.reset(); if (isNaN(m.heading)) { - tg.setFontAlign(0,-1); - tg.setFont("6x8",1); - tg.drawString("Uncalibrated",60,4); - tg.drawString("turn 360° around",60,12); + if (!wasUncalibrated) { + g.clearRect(0,24,W,48); + g.setFontAlign(0,-1).setFont("6x8"); + g.drawString("Uncalibrated\nturn 360° around",M,24+4); + wasUncalibrated = true; + } + } else { + if (wasUncalibrated) { + g.clearRect(0,24,W,48); + wasUncalibrated = false; + } + g.setFontAlign(0,0).setFont("6x8",3); + var y = 36; + g.clearRect(M-40,y,M+40,y+24); + g.drawString(Math.round(m.heading),M,y,true); } - else { - tg.setFontAlign(0,0); - tg.setFont("6x8",2); - tg.drawString(Math.round(m.heading),60,12); - } - g.drawImage(timg,0,0,{scale:2}); + ag.setColor(0); arrow(oldHeading,0); arrow(oldHeading+180,0); arrow(m.heading,2); arrow(m.heading+180,3); - g.drawImage(aimg,40,50); + g.drawImage(aimg, + (W-ag.getWidth())/2, + g.getHeight()-(ag.getHeight()+4)); oldHeading = m.heading; }); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); Bangle.setCompassPower(1); +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/fwupdate/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/fwupdate/app.png b/apps/fwupdate/app.png new file mode 100644 index 000000000..1fabf06a2 Binary files /dev/null and b/apps/fwupdate/app.png differ diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html new file mode 100644 index 000000000..5286ef062 --- /dev/null +++ b/apps/fwupdate/custom.html @@ -0,0 +1,284 @@ + + + + + +
+

Firmware updates using the App Loader are only possible on + Bangle.js 2. For firmware updates on Bangle.js 1 please + see the Bangle.js 1 instructions

+
+ + +

+
+    
+
+    
+  
+
diff --git a/apps/gpstime/ChangeLog b/apps/gpstime/ChangeLog
index a3bd6351e..4d9bbc8a2 100644
--- a/apps/gpstime/ChangeLog
+++ b/apps/gpstime/ChangeLog
@@ -1,2 +1,3 @@
 0.03: Fix time output on new firmwares when no GPS time set (fix #104)
-0.04: Fix shown UTC time zone sign
\ No newline at end of file
+0.04: Fix shown UTC time zone sign
+0.05: Use new 'layout library for Bangle2, fix #764 by adding a back button
diff --git a/apps/gpstime/gpstime-icon.js b/apps/gpstime/gpstime-icon.js
index 665c8d5f6..99998c6c4 100644
--- a/apps/gpstime/gpstime-icon.js
+++ b/apps/gpstime/gpstime-icon.js
@@ -1 +1 @@
-require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="))
+require("heatshrink").decompress(atob("mEw4UA////G161hyd8Jf4ALlQLK1WABREC1WgBZEK32oFxPW1QuJ7QwIFwOqvQLHhW31NaBY8qy2rtUFoAuG3W61EVqALF1+qr2gqtUHQu11dawNVqo6F22q9XFBYIwEhWqz2r6oLBGAheBqwuBBYx2CFwQLGlWqgoLCMAsKLoILChR6EgQuDqkqYYsBFweqYYoLDoWnYYoLD/WVYYv8FwXqPoIwEn52BqGrPoILEh/1FwOl9SsBBYcD/pdB2uq/QvEh/8LoOu1xHFh8/gGp9WWL4oMBgWltXeO4owBgWt1ReFYYh2GYYmXEQzDD3wiHegYKIGAJRGAAguJAH4AC"))
diff --git a/apps/gpstime/gpstime.js b/apps/gpstime/gpstime.js
index a061d2e23..8c80953fa 100644
--- a/apps/gpstime/gpstime.js
+++ b/apps/gpstime/gpstime.js
@@ -1,68 +1,75 @@
-var img = require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA="));
+function satelliteImage() {
+  return require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AGnE4F1wvsF34wgFldcLdyMYsoACF1WJF4YxPFzOtF4wxNFzAvKSiIvU1ovIGAkJAAQucF5QxCFwYwbF4QwLrwvjYIVfrwABrtdq9Wqwvkq4oCAAtXmYvi1teE4NXrphCrxoCGAbvdSIoAHNQNeFzQvGeRQvCsowrYYNfF8YwHZQQFCF8QwGF4owjeYovBroHEMERhEF8IwNrtWryYFF8YwCq4vhGBeJF5AwaxIwKwVXFwwvandfMJeJF8M6nZiLGQIvdstfGAVlGBZkCxJeZJQIwCGIRjMFzYACGIc6r/+FsIvGGIYABEzYvPGQYvusovkAH4A/AH4A/ACo="));
+}
+
+var fix;
 
 Bangle.setLCDPower(1);
 Bangle.setLCDTimeout(0);
+var Layout = require("Layout");
+Bangle.setGPSPower(1, "app");
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+E.showMessage("Loading..."); // avoid showing rubbish on screen
 
-g.clear();
-
-var fix;
-Bangle.setGPSPower(1);
-Bangle.on('GPS',function(f) {
-  fix = f;
-  g.reset(1);
-  g.setFont("6x8",2);
-  g.setFontAlign(0,0);
-  g.clearRect(90,30,239,90);
-  if (fix.fix) {
-    g.drawString("GPS",170,40);
-    g.drawString("Acquired",170,60);
+function setGPSTime() {
+  if (fix.time!==undefined) {
+    setTime(fix.time.getTime()/1000);
+    E.showMessage("System time set", {img:require("heatshrink").decompress(atob("lEo4UBvvv///vEFBYNVAAWq1QFDBAgKGrQJD0oJDtQJD1IICqwGBFoIDByocDwAJBgQeDtWoJwcqDwWq0EAgfAgEKHoQcCBIQeBGAQaBBIQzBytaEwQJDlWlrQmBBIkK0tqBI+ptRNCBIcCBKhECBIh6CAgUL8AJHl/4BI8+3gJRl/8GJH/BI8Ah6MDLIZQB+BjGAAIoBBI84BIaVCAAaVBVIYJEWYLkEXobRDAAbRBcoYACcoT5DEwYJCtQoElWpBINaDwYcB0oJBGQIzCAYIwBDwQGBAAIcCDwYACDgQACBIYIEBQYFDA="))});
   } else {
-    g.drawString("Waiting for",170,40);
-    g.drawString("GPS Fix",170,60);
+    E.showMessage("No GPS time to set");
   }
-  g.setFont("6x8");
-  g.drawString(fix.satellites+" satellites",170,80);
 
-  g.clearRect(0,100,239,239);
-  var t = ["","","","---",""];
-  if (fix.time!==undefined)
+  Bangle.removeListener('GPS',onGPS);
+  setTimeout(function() {
+    fix = undefined;
+    layout.forgetLazyState(); // redraw all next time
+    Bangle.on('GPS',onGPS);
+  }, 2000);
+}
+
+var layout = new Layout( {
+  type:"v", c: [
+    {type:"h", c:[
+      {type:"img", src:satelliteImage },
+      { type:"v", fillx:1, c: [
+       {type:"txt", font:"6x8:2", label:"Waiting\nfor GPS", id:"status" },
+       {type:"txt", font:"6x8", label:"---", id:"sat" },
+     ]},
+    ]},
+    {type:"txt", fillx:1, filly:1, font:"6x8:2", label:"---", id:"gpstime" }
+  ]},{lazy:true, btns: [
+    { label : "Set", cb : setGPSTime},
+    { label : "Back", cb : ()=>load() }
+  ]});
+
+
+function onGPS(f) {
+  if (fix===undefined) {
+    g.clear();
+    Bangle.drawWidgets();
+  }
+  fix = f;
+  if (fix.fix) {
+    layout.status.label = "GPS\nAcquired";
+  } else {
+    layout.status.label = "Waiting\nfor GPS";
+  }
+  layout.sat.label = fix.satellites+" satellites";
+
+  var t = ["","---",""];
+  if (fix.time!==undefined) {
     t = fix.time.toString().split(" ");
-    /*
- [
-  "Sun",
-  "Nov",
-  "10",
-  "2019",
-  "15:55:35",
-  "GMT+0100"
- ]
-  */
-  //g.setFont("6x8",2);
-  //g.drawString(t[0],120,110); // day
-  g.setFont("6x8",3);
-  g.drawString(t[1]+" "+t[2],120,135); // date
-  g.setFont("6x8",2);
-  g.drawString(t[3],120,160); // year
-  g.setFont("6x8",3);
-  g.drawString(t[4],120,185); // time
-  if (fix.time) {
-    // timezone
     var tz = (new Date()).getTimezoneOffset()/-60;
     if (tz==0) tz="UTC";
     else if (tz>0) tz="UTC+"+tz;
     else tz="UTC"+tz;
-    g.setFont("6x8",2);
-    g.drawString(tz,120,210); // gmt
-    g.setFontAlign(0,0,3);
-    g.drawString("Set",230,120);
-    g.setFontAlign(0,0);
-  }
-});
 
-setInterval(function() {
-  g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});
-},100);
-setWatch(function() {
-  if (fix.time!==undefined)
-    setTime(fix.time.getTime()/1000);
-}, BTN2, {repeat:true});
+    t = [t[1]+" "+t[2],t[3],t[4],t[5],tz];
+  }
+
+  layout.gpstime.label = t.join("\n");
+  layout.render();
+}
+
+Bangle.on('GPS',onGPS);
diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog
index 0ca30d066..aaa55d01a 100644
--- a/apps/hcclock/ChangeLog
+++ b/apps/hcclock/ChangeLog
@@ -1,2 +1,2 @@
 0.01: base code
-
+0.02: saved settings when switching color scheme
\ No newline at end of file
diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js
index 98abbc6f3..4664dd763 100644
--- a/apps/hcclock/hcclock.app.js
+++ b/apps/hcclock/hcclock.app.js
@@ -174,19 +174,52 @@ function fmtDate(day,month,year,hour)
     let ap = "(AM)";
     if(hour == 0 || hour > 12)
       ap = "(PM)";
-    return months[month] + " " + day + " " + year + " "+ ap;    
+    return months[month] + " " + day + " " + year + " "+ ap;
   }
   else
     return months[month] + ". " + day + " " + year;
 }
 
-// Handles Flipping colors, then refreshes the UI
+
+//////////////////////////////////////////
+//
+//  HANDLE COLORS + SETTINGS
+//
+
+function getColorScheme()
+{
+    let settings = require('Storage').readJSON("hcclock.json", true) || {};
+    if (!("scheme" in settings)) {
+      settings.scheme = 0;
+    }
+    return settings.scheme;
+}
+
+function setColorScheme(value)
+{
+    let settings = require('Storage').readJSON("hcclock.json", true) || {};
+    settings.scheme = value;
+    require('Storage').writeJSON('hcclock.json', settings);
+
+    if(value == 0) // White
+    {
+      bg = 255;
+      fg = 0;
+    }
+    else // Black
+    {
+      bg = 0;
+      fg = 255;
+    }
+    redraw();
+}
+
 function flipColors()
 {
-  let t = bg;
-  bg = fg;
-  fg = t;
-  redraw();
+  if(getColorScheme() == 0)
+      setColorScheme(1);
+  else
+      setColorScheme(0);
 }
 
 //////////////////////////////////////////
@@ -197,7 +230,7 @@ function flipColors()
 // Initialize
 g.clear();
 Bangle.loadWidgets();
-redraw();
+setColorScheme(getColorScheme());
 
 // Define Refresh Interval
 setInterval(updateTime, interval);
diff --git a/apps/heart/ChangeLog b/apps/heart/ChangeLog
index 8274169ee..f6fd9793e 100644
--- a/apps/heart/ChangeLog
+++ b/apps/heart/ChangeLog
@@ -12,3 +12,4 @@
       Generate scale based on defined minimum and maximum measurement
       Added background line on 50% to ease estimation of drawn values
 0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
+0.07: theme support
diff --git a/apps/heart/app.js b/apps/heart/app.js
index 77a1c2106..5428ea06b 100644
--- a/apps/heart/app.js
+++ b/apps/heart/app.js
@@ -221,9 +221,9 @@ function graphRecord(n) {
     if (tempCount == startLine) {
       // generating rgaph in loop when reaching startLine to keep loading
       // message on screen until graph can be drawn
-      g.clear().
+      g.reset().clearRect(0,24,g.getWidth(),g.getHeight()).
       // Home for Btn2
-        setColor(1, 1, 1).
+        setColor(g.theme.fg).
         drawLine(220, 118, 227, 110).
         drawLine(227, 110, 234, 118).
         drawPoly([222,117,222,125,232,125,232,117], false).
@@ -245,7 +245,7 @@ function graphRecord(n) {
         // scale indicator line for 50%
         drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2).
         // background line for 50%
-        setColor(1, 1, 1).
+        setColor(g.theme.fg).
         drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2).
         setFontAlign(1, -1, 0).
         setFont("Vector", 10);
@@ -303,7 +303,7 @@ function graphRecord(n) {
   log("Finished rendering data");
   Bangle.buzz(200, 0.3);
   g.flip();
-  setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
+  setWatch(stop, (global.BTN2!==undefined)?BTN2:BTN1, {edge:"falling", debounce:50, repeat:false});
   return;
 }
 
diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog
index d27886b15..9b390b63e 100644
--- a/apps/hrm/ChangeLog
+++ b/apps/hrm/ChangeLog
@@ -3,3 +3,4 @@
 0.03: Fix timing issues, and use 1/2 scale to keep graph on screen
 0.04: Update for new firmwares that have a 'HRM-raw' event
 0.05: Tweaks for 'HRM-raw' handling
+0.06: Add widgets
diff --git a/apps/hrm/heartrate-icon.js b/apps/hrm/heartrate-icon.js
index cadbc7dfa..20c9b15f7 100644
--- a/apps/hrm/heartrate-icon.js
+++ b/apps/hrm/heartrate-icon.js
@@ -1 +1 @@
-require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA="))
+require("heatshrink").decompress(atob("mEw4UA///g3yrv/7f+Jf4AJgNVoAEGAANVAAIEGCIQABoAEEBYMFAwVQAggLBioGCqgEEFIgAGFwdXBYw1Dr4LKrwLHIIVaBYxNDvXVBanVteVBZGVt+VKooLBq+19u1JItQgNW0vlBYIxEL4Ne1u18taGIN9BYUD1XvBYN62+q1a0D1d7ytttYLEWYV6BYNt93VEYKzCita6t59vqX4sFIgN70tqa4pUBTgO1vbvFgB0BKQNZawYACdYNeytdFwgwCBYJ2DFwQwCqoxBFwwABBYoKEGAKyDFwgwDFw4kDERBVDEQ4kEEQ4kDBRAYBERBuCNAoA/AA4="))
diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js
index a6b8e791a..a47251010 100644
--- a/apps/hrm/heartrate.js
+++ b/apps/hrm/heartrate.js
@@ -4,13 +4,14 @@ Bangle.setHRMPower(1);
 var hrmInfo, hrmOffset = 0;
 var hrmInterval;
 var btm = g.getHeight()-1;
+var lastHrmPt = []; // last xy coords we draw a line to
 
 function onHRM(h) {
   if (counter!==undefined) {
     // the first time we're called remove
     // the countdown
     counter = undefined;
-    g.clear();
+    g.clearRect(0,24,g.getWidth(),g.getHeight());
   }
   hrmInfo = h;
   /* On 2v09 and earlier firmwares the only solution for realtime
@@ -28,7 +29,7 @@ function onHRM(h) {
 
   var px = g.getWidth()/2;
   g.setFontAlign(0,0);
-  g.clearRect(0,24,239,80);
+  g.clearRect(0,24,g.getWidth(),80);
   g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75);
   var str = hrmInfo.bpm;
   g.setFontVector(40).drawString(str,px,45);
@@ -43,17 +44,18 @@ Bangle.on('HRM-raw', function(v) {
   hrmOffset++;
   if (hrmOffset>g.getWidth()) {
     hrmOffset=0;
-    g.clearRect(0,80,239,239);
-    g.moveTo(-100,0);
+    g.clearRect(0,80,g.getWidth(),g.getHeight());
+    lastHrmPt = [-100,0];
   }
 
   y = E.clip(btm-v.filt/4,btm-10,btm);
   g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y);
   y = E.clip(170 - (v.raw/2),80,btm);
-  g.setColor(g.theme.fg).lineTo(hrmOffset, y);
+  g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
+  lastHrmPt = [hrmOffset, y];
   if (counter !==undefined) {
     counter = undefined;
-    g.clear();
+    g.clearRect(0,24,g.getWidth(),g.getHeight());
   }
 });
 
@@ -65,7 +67,10 @@ function countDown() {
     setTimeout(countDown, 1000);
   }
 }
-g.clear().setFont("6x8",2).setFontAlign(0,0);
+g.clear();
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+g.reset().setFont("6x8",2).setFontAlign(0,0);
 g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
 countDown();
 
@@ -79,13 +84,14 @@ function readHRM() {
   if (!hrmInfo) return;
 
   if (hrmOffset==0) {
-    g.clearRect(0,100,239,239);
-    g.moveTo(-100,0);
+    g.clearRect(0,100,g.getWidth(),g.getHeight());
+    lastHrmPt = [-100,0];
   }
   for (var i=0;i<2;i++) {
     var a = hrmInfo.raw[hrmOffset];
     hrmOffset++;
     y = E.clip(170 - (a*2),100,230);
-    g.setColor(g.theme.fg).lineTo(hrmOffset, y);
+    g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y);
+    lastHrmPt = [hrmOffset, y];
   }
 }
diff --git a/apps/launch/app.js b/apps/launch/app.js
index 449e16e62..3d4682e55 100644
--- a/apps/launch/app.js
+++ b/apps/launch/app.js
@@ -16,7 +16,7 @@ function drawMenu() {
   var w = g.getWidth();
   var h = g.getHeight();
   var m = w/2;
-  var n = (h-48)/64;
+  var n = Math.floor((h-48)/64);
   if (selected>=n+menuScroll) menuScroll = 1+selected-n;
   if (selectedm.id==msgid);
+  if (!msg) return checkMessages(); // go home if no message found
+  if (msg.src=="Maps") return showMapMessage(msg);
+  var m = msg.title+"\n"+msg.body;
+  E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => {
+    if (chosen=="read") {
+      // any input to mark as not new and return to menu
+      msg.new = false;
+      saveMessages();
+      checkMessages();
+    } else {
+      checkMessages(true);
+    }
+  });
+}
+
+// Show a single menu item for the message
+function showMessageMenuItem(y, idx) {
+  var msg = MESSAGES[idx];
+  var W = g.getWidth(), H=48;
+  if (msg.new) g.setBgColor("#4F4");
+  else g.setBgColor("#CFC");
+  g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg);
+  var m = msg.title+"\n"+msg.body;
+  if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2);
+  if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2);
+  if (msg.body) {
+    g.setFontAlign(-1,-1).setFont("6x8");
+    var l = g.wrapString(msg.body, W-14);
+    if (l.length>3) {
+      l = l.slice(0,3);
+      l[l.length-1]+="...";
+    }
+    g.drawString(l.join("\n"), 12,y+20);
+  }
+}
+//test
+//g.clear(1); showMessageMenuItem(MESSAGES[0],24)
+
+if (process.env.HWVERSION==1) { // Bangle.js 1
+  showBigMenu = function(options) {
+  /* options = {
+    h = height
+    items = # of items
+    draw = function(y, idx)
+    onSelect = function(idx)
+  }*/
+    var selected = 0;
+    var menuScroll = 0;
+    var menuShowing = false;
+    var w = g.getWidth();
+    var h = g.getHeight();
+    var m = w/2;
+    var n = Math.floor((h-48)/options.h);
+
+    function drawMenu() {
+      g.reset();
+      if (selected>=n+menuScroll) menuScroll = 1+selected-n;
+      if (selected=options.items) break;
+        var y = 24+i*options.h;
+        options.draw(y, idx);
+        // border for selected
+        if (i+menuScroll==selected) {
+          g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2);
+        }
+      }
+      // arrows
+      g.setColor(menuScroll ? g.theme.fg : g.theme.bg);
+      g.fillPoly([m,6,m-14,20,m+14,20]);
+      g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg);
+      g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]);
+    }
+    g.clearRect(0,24,w-1,h-1);
+    drawMenu();
+    Bangle.setUI("updown",dir=>{
+      if (dir) {
+        selected += dir;
+        if (selected<0) selected = options.items-1;
+        if (selected>=options.items) selected = 0;
+        drawMenu();
+      } else {
+        options.onSelect(selected);
+      }
+    });
+  }
+} else { // Bangle.js 2
+  showBigMenu = function(options) {
+    /* options = {
+      h = height
+      items = # of items
+      draw = function(y, idx)
+      onSelect = function(idx)
+    }*/
+    var menuScroll = 0;
+    var menuShowing = false;
+    var w = g.getWidth();
+    var h = g.getHeight();
+    var n = Math.ceil((h-24)/options.h);
+    var menuScrollMax = options.h*options.items - (h-24);
+
+    function drawItem(i) {
+      var y = 24+i*options.h-menuScroll;
+      if (i<0 || i>=options.items || y<-options.h || y>=h) return;
+      options.draw(y, i);
+    }
+
+    function drawMenu() {
+      g.reset().clearRect(0,24,w-1,h-1);
+      g.setClipRect(0,24,w-1,h-1);
+      for (var i=0;i{
+      var dy = e.dy;
+      if (menuScroll - dy < 0)
+        dy = menuScroll;
+      if (menuScroll - dy > menuScrollMax)
+        dy = menuScroll - menuScrollMax;
+      if (!dy) return;
+      g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1);
+      g.scroll(0,dy);
+      menuScroll -= dy;
+      if (e.dy < 0) {
+        drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1);
+        if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2);
+      } else {
+        drawItem(Math.floor((menuScroll+24)/options.h));
+        if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1);
+      }
+      g.setClipRect(0,0,w-1,h-1);
+    };
+    Bangle.on('drag',Bangle.dragHandler);
+    Bangle.touchHandler = (_,e)=>{
+      if (e.y<20) return;
+      var i = Math.floor((e.y+menuScroll-24) / options.h);
+      if (i>=0 && i { load() });
+  // we have >0 messages
+  // TODO: IF A NEW MESSAGE, SHOW IT
+  if (!forceShowMenu) {
+    var newMessages = MESSAGES.filter(m=>m.new);
+    if (newMessages.length)
+      return showMessage(newMessages[0].id);
+  }
+  // Otherwise show a menu
+  var m = {
+    "":{title:"Messages"},
+    "< Back": ()=>load()
+  };
+  /*g.setFont("6x8");
+  MESSAGES.forEach(msg=>{
+    // "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"
+    var title = g.wrapString(msg.title, g.getWidth())[0];
+    m[title] = function() {
+      showMessage(msg.id);
+    }
+  });
+  E.showMenu(m);*/
+  showBigMenu({
+    h : 48,
+    items : MESSAGES.length,
+    draw : showMessageMenuItem,
+    onSelect : idx => showMessage(MESSAGES[idx].id)
+  });
+}
+
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+checkMessages();
diff --git a/apps/messages/app.png b/apps/messages/app.png
new file mode 100644
index 000000000..c9177692e
Binary files /dev/null and b/apps/messages/app.png differ
diff --git a/apps/messages/boot.js b/apps/messages/boot.js
new file mode 100644
index 000000000..dce3979da
--- /dev/null
+++ b/apps/messages/boot.js
@@ -0,0 +1,36 @@
+(function() {
+  var _GB = global.GB;
+  global.GB = (event) => {
+    if (_GB) setTimeout(_GB,0,event);
+    // call handling?
+    if (!event.t.startsWith("notify")) return;
+    /* event is:
+      {t:"notify",id:int, src,title,subject,body,sender,tel:string}
+      {t:"notify~",id:int, title:string} // modified
+      {t:"notify-",id:int} // remove
+    */
+    var messages, inApp = "undefined"!=typeof MESSAGES;
+    if (inApp)
+      messages = MESSAGES; // we're in an app that has already loaded messages
+    else   // no app - load messages
+      messages = require("Storage").readJSON("messages.json",1)||[];
+    // now modify/delete as appropriate
+    var mIdx = messages.findIndex(m=>m.id==event.id);
+    if (event.t=="notify-") {
+      if (mIdx>=0) messages.splice(mIdx, 1); // remove item
+      mIdx=-1;
+    } else { // add/modify
+      if (event.t=="notify") event.new=true; // new message
+      if (mIdx<0) mIdx=messages.push(event)-1;
+      else Object.assign(messages[mIdx], event);
+    }
+    require("Storage").writeJSON("messages.json",messages);
+    if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]);
+    // ok, saved now - we only care if it's new
+    if (event.t!="notify") return;
+    // if we're in a clock, go straight to messages app
+    if (Bangle.CLOCK) return load("messages.app.js");
+    if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know
+    WIDGETS.messages.newMessage();
+  };
+})()
diff --git a/apps/messages/lib.js b/apps/messages/lib.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/messages/widget.js b/apps/messages/widget.js
new file mode 100644
index 000000000..eda4a85a5
--- /dev/null
+++ b/apps/messages/widget.js
@@ -0,0 +1,20 @@
+WIDGETS["messages"]={area:"tl",width:0,draw:function() {
+  if (!this.width) return;
+  var c = (Date.now()-this.t)/1000;
+  g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff");
+  g.clearRect(this.x,this.y,this.x+this.width,this.y+23);
+  g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12);
+  //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
+  if (c<120 && (Date.now()-this.l)>4000) {
+    this.l = Date.now();
+    Bangle.buzz(); // buzz every 4 seconds
+  }
+  setTimeout(()=>WIDGETS["messages"].draw(), 1000);
+},newMessage:function() {
+  WIDGETS["messages"].t=Date.now(); // first time
+  WIDGETS["messages"].l=Date.now()-10000; // last buzz
+  if (WIDGETS["messages"].c!==undefined) return; // already called
+  WIDGETS["messages"].width=64;
+  Bangle.drawWidgets();
+  Bangle.setLCDPower(1);// turns screen on
+}};
diff --git a/apps/sclock/ChangeLog b/apps/sclock/ChangeLog
index 44a0ec504..dc76b8299 100644
--- a/apps/sclock/ChangeLog
+++ b/apps/sclock/ChangeLog
@@ -3,3 +3,4 @@
 0.04: Make this clock do 12h and 24h
 0.05: setUI, screen size changes
 0.06: Use Bangle.setUI for button/launcher handling
+0.07: Update *on* the minute rather than every 15 secs
diff --git a/apps/sclock/clock-simple.js b/apps/sclock/clock-simple.js
index 8fb204d22..a399b05a7 100644
--- a/apps/sclock/clock-simple.js
+++ b/apps/sclock/clock-simple.js
@@ -1,4 +1,3 @@
-/* jshint esversion: 6 */
 const big = g.getWidth()>200;
 const timeFontSize = big?6:5;
 const dateFontSize = big?3:2;
@@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9;
 // Check settings for what type our clock should be
 var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
 
-function drawSimpleClock() {
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+  if (drawTimeout) clearTimeout(drawTimeout);
+  drawTimeout = setTimeout(function() {
+    drawTimeout = undefined;
+    draw();
+  }, 60000 - (Date.now() % 60000));
+}
+
+function draw() {
   // get date
   var d = new Date();
   var da = d.toString().split(" ");
@@ -60,11 +71,18 @@ function drawSimpleClock() {
   var gmt = da[5];
   g.setFont(font, gmtFontSize);
   g.drawString(gmt, xyCenter, yposGMT, true);
+
+  queueDraw();
 }
 
-// handle switch display on by pressing BTN1
-Bangle.on('lcdPower', function(on) {
-  if (on) drawSimpleClock();
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+  if (on) {
+    draw(); // draw immediately, queue redraw
+  } else { // stop draw timer
+    if (drawTimeout) clearTimeout(drawTimeout);
+    drawTimeout = undefined;
+  }
 });
 
 // clean app screen
@@ -74,8 +92,5 @@ Bangle.setUI("clock");
 Bangle.loadWidgets();
 Bangle.drawWidgets();
 
-// refesh every 15 sec
-setInterval(drawSimpleClock, 15E3);
-
 // draw now
-drawSimpleClock();
+draw();
diff --git a/apps/simplest/ChangeLog b/apps/simplest/ChangeLog
index d69da4ddc..f37015d6a 100644
--- a/apps/simplest/ChangeLog
+++ b/apps/simplest/ChangeLog
@@ -1,2 +1,3 @@
 0.01: Modified for use with new bootloader and firmware
 0.02: Use Bangle.setUI for button/launcher handling
+0.03: Fix display for Bangle 2
diff --git a/apps/simplest/app.js b/apps/simplest/app.js
index 2ed4e5580..68564ff33 100644
--- a/apps/simplest/app.js
+++ b/apps/simplest/app.js
@@ -1,14 +1,17 @@
 
+const h = g.getHeight();
+const w = g.getWidth();
+
 function draw() {
   var d = new Date();
   var da = d.toString().split(" ");
   var time = da[4].substr(0,5);
 
   g.reset();
-  g.clearRect(0, 30, 239, 99);
+  g.clearRect(0, 30, w, 99);
   g.setFontAlign(0, -1);
-  g.setFont("Vector", 80);
-  g.drawString(time, 120, 40);
+  g.setFont("Vector", w/3);
+  g.drawString(time, w/2, 40);
 }
 
 // handle switch display on by pressing BTN1
diff --git a/apps/stopwatch/A.jpg b/apps/stopwatch/A.jpg
new file mode 100644
index 000000000..9155b9986
Binary files /dev/null and b/apps/stopwatch/A.jpg differ
diff --git a/apps/stopwatch/B.jpg b/apps/stopwatch/B.jpg
new file mode 100644
index 000000000..639ff5d42
Binary files /dev/null and b/apps/stopwatch/B.jpg differ
diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog
new file mode 100644
index 000000000..9db0e26c5
--- /dev/null
+++ b/apps/stopwatch/ChangeLog
@@ -0,0 +1 @@
+0.01: first release
diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md
new file mode 100644
index 000000000..30a9306d1
--- /dev/null
+++ b/apps/stopwatch/README.md
@@ -0,0 +1,33 @@
+# Stopwatch Touch
+
+A touch screen based stop watch for Bangle 2
+
+## Screenshots
+
+![](screenshot1.png)
+![](screenshot2.png)
+![](screenshot3.png)
+
+## Features
+
+* Attractive UI design
+* Will run up to 99 hours
+* Shows 10th of seconds up to 1 hour
+* Start / Pause button
+* Reset button
+
+## Future features
+
+I'm keen to complete this project with
+
+* Ability to dismiss the app and leave it running in the background
+* A small widget to show the elapsed time on the current active clock
+* Laptimes, with a way to view all the laptimes on a scrollable screen
+
+
+## One of these is a genuine Bangle Js 2 Open Source Smartwatch, the other isn't
+
+Which one is which ?
+
+![](A.jpg)
+![](B.jpg)
diff --git a/apps/stopwatch/pause-24.png b/apps/stopwatch/pause-24.png
new file mode 100644
index 000000000..eb3d8feaa
Binary files /dev/null and b/apps/stopwatch/pause-24.png differ
diff --git a/apps/stopwatch/pause-24a.png b/apps/stopwatch/pause-24a.png
new file mode 100644
index 000000000..7838ef640
Binary files /dev/null and b/apps/stopwatch/pause-24a.png differ
diff --git a/apps/stopwatch/play-24.png b/apps/stopwatch/play-24.png
new file mode 100644
index 000000000..268b5dc31
Binary files /dev/null and b/apps/stopwatch/play-24.png differ
diff --git a/apps/stopwatch/screenshot1.png b/apps/stopwatch/screenshot1.png
new file mode 100644
index 000000000..6d94ce05c
Binary files /dev/null and b/apps/stopwatch/screenshot1.png differ
diff --git a/apps/stopwatch/screenshot2.png b/apps/stopwatch/screenshot2.png
new file mode 100644
index 000000000..0baa73331
Binary files /dev/null and b/apps/stopwatch/screenshot2.png differ
diff --git a/apps/stopwatch/screenshot3.png b/apps/stopwatch/screenshot3.png
new file mode 100644
index 000000000..1e7cfca58
Binary files /dev/null and b/apps/stopwatch/screenshot3.png differ
diff --git a/apps/stopwatch/stop-24.png b/apps/stopwatch/stop-24.png
new file mode 100644
index 000000000..658d614ca
Binary files /dev/null and b/apps/stopwatch/stop-24.png differ
diff --git a/apps/stopwatch/stop-24a.png b/apps/stopwatch/stop-24a.png
new file mode 100644
index 000000000..e89ddae05
Binary files /dev/null and b/apps/stopwatch/stop-24a.png differ
diff --git a/apps/stopwatch/stopwatch.app.js b/apps/stopwatch/stopwatch.app.js
new file mode 100644
index 000000000..48d4f26ea
--- /dev/null
+++ b/apps/stopwatch/stopwatch.app.js
@@ -0,0 +1,220 @@
+let w = g.getWidth();
+let h = g.getHeight();
+let tTotal = Date.now();
+let tStart = tTotal;
+let tCurrent = tTotal;
+let running = false;
+let timeY = 2*h/5;
+let displayInterval;
+let redrawButtons = true;
+const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
+
+// 24 pixel images, scale to watch
+// 1 bit optimal, image string, no E.toArrayBuffer()
+const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
+const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
+const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
+
+function log_debug(o) {
+  //console.log(o);
+}
+
+function timeToText(t) {
+  let hrs = Math.floor(t/3600000);
+  let mins = Math.floor(t/60000)%60;
+  let secs = Math.floor(t/1000)%60;
+  let tnth = Math.floor(t/100)%10;
+  let text;
+
+  if (hrs === 0) 
+    text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
+  else
+    text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
+
+  //log_debug(text);
+  return text;
+}
+
+function drawButtons() {
+  log_debug("drawButtons()");
+  if (!running && tCurrent == tTotal) {
+    bigPlayPauseBtn.draw();
+  } else if (!running && tCurrent != tTotal) {
+    resetBtn.draw();
+    smallPlayPauseBtn.draw();
+  } else {
+    bigPlayPauseBtn.draw();
+  }
+  
+  redrawButtons = false;
+}
+
+function drawTime() {
+  log_debug("drawTime()");
+  let Tt = tCurrent-tTotal;
+  let Ttxt = timeToText(Tt);
+
+  // total time
+  g.setFont("Vector",38);  // check
+  g.setFontAlign(0,0);
+  g.clearRect(0, timeY - 21, w, timeY + 21);
+  g.setColor(g.theme.fg); 
+  g.drawString(Ttxt, w/2, timeY);
+}
+
+function draw() {
+  let last = tCurrent;
+  if (running) tCurrent = Date.now();
+  g.setColor(g.theme.fg);
+  if (redrawButtons) drawButtons();
+  drawTime();
+}
+
+function startTimer() {
+  log_debug("startTimer()");
+  draw();
+  displayInterval = setInterval(draw, 100);
+}
+
+function stopTimer() {
+  log_debug("stopTimer()");
+  if (displayInterval) {
+    clearInterval(displayInterval);
+    displayInterval = undefined;
+  }
+}
+
+// BTN stop start
+function stopStart() {
+  log_debug("stopStart()");
+
+  if (running)
+    stopTimer();
+
+  running = !running;
+  Bangle.buzz();
+
+  if (running)
+    tStart = Date.now() + tStart- tCurrent;  
+  tTotal = Date.now() + tTotal - tCurrent;
+  tCurrent = Date.now();
+
+  setButtonImages();
+  redrawButtons = true;
+  if (running) {
+    startTimer();
+  } else {
+    draw();
+  }
+}
+
+function setButtonImages() {
+  if (running) {
+    bigPlayPauseBtn.setImage(pause_img);
+    smallPlayPauseBtn.setImage(pause_img);
+    resetBtn.setImage(reset_img);
+  } else {
+    bigPlayPauseBtn.setImage(play_img);
+    smallPlayPauseBtn.setImage(play_img);
+    resetBtn.setImage(reset_img);
+  }
+}
+
+// lap or reset
+function lapReset() {
+  log_debug("lapReset()");
+  if (!running && tStart != tCurrent) {
+    redrawButtons = true;
+    Bangle.buzz();
+    tStart = tCurrent = tTotal = Date.now();
+    g.clearRect(0,24,w,h);
+    draw();
+  }
+}
+
+// simple on screen button class
+function BUTTON(name,x,y,w,h,c,f,i) {
+  this.name = name;
+  this.x = x;
+  this.y = y;
+  this.w = w;
+  this.h = h;
+  this.color = c;
+  this.callback = f;
+  this.img = i;
+}
+
+BUTTON.prototype.setImage = function(i) {
+  this.img = i;
+}
+
+// if pressed the callback
+BUTTON.prototype.check = function(x,y) {
+  //console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
+  
+  if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) {
+    log_debug(this.name + ":callback\n");
+    this.callback();
+    return true;
+  }
+  return false;
+};
+
+BUTTON.prototype.draw = function() {
+  g.setColor(this.color);
+  g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
+  g.setColor("#000"); // the icons and boxes are drawn black
+  if (this.img != undefined) {
+    let iw = iconScale * 24;  // the images were loaded as 24 pixels, we will scale
+    let ix = this.x + ((this.w - iw) /2);
+    let iy = this.y + ((this.h - iw) /2);
+    log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})");
+    g.drawImage(this.img, ix, iy, {scale: iconScale}); 
+  }
+  g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h);
+};
+
+
+var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img);
+var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img);
+var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img);
+
+bigPlayPauseBtn.setImage(play_img);
+smallPlayPauseBtn.setImage(play_img);
+resetBtn.setImage(pause_img);
+
+
+Bangle.on('touch', function(button, xy) {
+  // not running, and reset
+  if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return;
+
+  // paused and hit play
+  if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return;
+
+  // paused and press reset
+  if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return;
+
+  // must be running
+  if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return;
+});
+
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+  if (on) {
+    draw(); // draw immediately, queue redraw
+  } else { // stop draw timer
+    if (drawTimeout) clearTimeout(drawTimeout);
+    drawTimeout = undefined;
+  }
+});
+
+// Clear the screen once, at startup
+g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
+// above not working, hence using next 2 lines
+g.setColor("#000");
+g.fillRect(0,0,w,h);
+
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+draw();
+Bangle.setUI("clock"); // Show launcher when button pressed
diff --git a/apps/stopwatch/stopwatch.icon.js b/apps/stopwatch/stopwatch.icon.js
new file mode 100644
index 000000000..867b95bd2
--- /dev/null
+++ b/apps/stopwatch/stopwatch.icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="));
diff --git a/apps/stopwatch/stopwatch.png b/apps/stopwatch/stopwatch.png
new file mode 100644
index 000000000..92ffe73b7
Binary files /dev/null and b/apps/stopwatch/stopwatch.png differ
diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog
index 4db60ecd5..fb71fbeb8 100644
--- a/apps/svclock/ChangeLog
+++ b/apps/svclock/ChangeLog
@@ -1,3 +1,4 @@
 0.01: Modification of SimpleClock 0.04 to use Vectorfont
 0.02: Use Bangle.setUI for button/launcher handling
-0.03: Scale to BangleJS 2 and add locale
\ No newline at end of file
+0.03: Scale to BangleJS 2 and add locale
+0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs
diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js
index 637701a3f..e08c6fa2c 100644
--- a/apps/svclock/vclock-simple.js
+++ b/apps/svclock/vclock-simple.js
@@ -1,4 +1,3 @@
-/* jshint esversion: 6 */
 const locale = require("locale");
 
 var timeFontSize;
@@ -12,8 +11,7 @@ var yposDate;
 var yposYear;
 var yposGMT;
 
-switch (process.env.BOARD) {
-  case "EMSCRIPTEN":
+if (g.getWidth() > 200) {
     timeFontSize = 65;
     dateFontSize = 20;
     gmtFontSize = 10;
@@ -22,8 +20,7 @@ switch (process.env.BOARD) {
     yposDate = 130;
     yposYear = 175;
     yposGMT = 220;
-    break;
-  case "EMSCRIPTEN2":
+} else {
     timeFontSize = 48;
     dateFontSize = 15;
     gmtFontSize = 10;
@@ -32,12 +29,23 @@ switch (process.env.BOARD) {
     yposDate = 95;
     yposYear = 128;
     yposGMT = 161;
-    break;
 }
 // Check settings for what type our clock should be
 var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
 
-function drawSimpleClock() {
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+  if (drawTimeout) clearTimeout(drawTimeout);
+  drawTimeout = setTimeout(function() {
+    drawTimeout = undefined;
+    draw();
+  }, 60000 - (Date.now() % 60000));
+}
+
+function draw() {
   g.clear();
   Bangle.drawWidgets();
 
@@ -76,23 +84,26 @@ function drawSimpleClock() {
   // draw gmt
   g.setFont(font, gmtFontSize);
   g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true);
+
+  queueDraw();
 }
 
-// handle switch display on by pressing BTN1
-Bangle.on('lcdPower', function(on) {
-  if (on) drawSimpleClock();
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+  if (on) {
+    draw(); // draw immediately, queue redraw
+  } else { // stop draw timer
+    if (drawTimeout) clearTimeout(drawTimeout);
+    drawTimeout = undefined;
+  }
 });
 
+// Show launcher when button pressed
+Bangle.setUI("clock");
 // clean app screen
 g.clear();
 Bangle.loadWidgets();
 Bangle.drawWidgets();
 
-// refesh every 15 sec
-setInterval(drawSimpleClock, 15E3);
-
 // draw now
-drawSimpleClock();
-
-// Show launcher when button pressed
-Bangle.setUI("clock");
+draw();
diff --git a/apps/vernierrespirate/ChangeLog b/apps/vernierrespirate/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/vernierrespirate/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/vernierrespirate/README.md b/apps/vernierrespirate/README.md
new file mode 100644
index 000000000..54dfc274b
--- /dev/null
+++ b/apps/vernierrespirate/README.md
@@ -0,0 +1,26 @@
+# Vernier Go Direct Respiration Belt
+
+Connects to a [Go Direct Respiration Belt](https://www.vernier.com/product/go-direct-respiration-belt/) via Bluetooth and shows respiration rate
+
+![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALAAAACwCAYAAACvt+ReAAAAAXNSR0IArs4c6QAADldJREFUeF7tnet2GysMhZP3f+h02TEuJoD2FuKu/jhntSOELh8awYzt75+fn58v/+MR2DQC3w7wpplzs58RcIAvBuH7ScDXV+ke/P39lFjmT65ZcICXSc94Q558PhrIAsQO8Pic+IxEBCJ+sxAHgGdvk2p2eAUmEn6a6KMCh/Yh1044wKdl/DB/QgUObqUQpwB/P3oNoz8/z94F++MVGIvT0VL5Cvu5gQs9cUDLAT4aif2c+1NhX3u452HEqxzHddEB3i/Hx1scQxx64By8v1D/tgxhE+ctxPF47OFggDgcoT3+n+tIVwM4je5jYfkpxB7MmVsZQ1zaTjnA5mF3hVYRkB5irNhCeAW2yv7memJ4kSdxq/TADvDm4FmYH2/Y2CdxszdxDrAFARvr+HvOyz2Jmw2wv8yzMXytpmfPeaNz4GfP+/rP+/GyH6O1ht3H94xA/C5E7mHGaqcQXoF70rCh7vhdCORJ3CwX/V2IWZFffF72SdwsdxzgWZFffF7pYYa/0L54Am83r/QORIiLA3w7Ie5/9wj4uxDdQ+wT9IzA8gCv8rGWnklw3foIOMD62PnIBSJwLcBe2Regz8CELQB+vrj8/f3+ZICB32991notbHMdeASuBDiG1gHGYVlR0gE2ruwrJvlkm5YGuEelzFXcE6pw+tBh9rfpjFo0DvDr07c7JTz3hCy2/4QFiS6AbQB+OGSRmBMqsBQH6ToKxw5yDrDRwhiVbARORGaUvb3ncYAHAGwJFKoLlUMAs9SFzMfIOMCvaPVKUtBrpR/Vg8pJsKz+wMcUYEtnSwloTUwvvSUQrE9SUP9ROQTgHg+SpHnR62YAj6o0LYmpjW3Ri8AbZFrmYcYysoj9FvpQKBk5c4AtTgt6gdZLLwJALKOFgRnHyObsT8e36mOgZGRNALZ2thdoUhKk62hgpVZKOw8zjpF1gDOPY7UBlMZJ12uQSWOl6wzA0oMRzVzMGEY29as0tkUnGjtWrrkCWzuLBAmRQarKh8z37/fOSOBJAWZs6yXb0sb1uvtJcdNebwJYSoB0HV35WjloXPgttPDKZhik/AHTrM/pN4hEhj3lH38X5mNjqYXYAU6TQ4CAJgmVEwHOgAXrjr9UIfLxPT7+kUAGzgLsH3ZVFsTT59d1dHGEOEm+S9fRKmql5+nq710T/8UY1Fm2AjBOMbJxUBEIVLojWN/fdkPE9M+cCaCI3dWWSQKeeBqpik9inIWON4c9AWYghpyyAkW6pRPwFRcIWo5K8OR8BVqN6oKtjIfiT4Bech+dBw2fqgIzRqCyolypKkU9bNXpsEkDIBBtKUykHSctdK1etB9n9DOy1bsCSqggRwPMOoDIizItvaqiJxft2Rng0CMHH5DPG0bxf/fVQCEQ9yAGEHcHWKou4nVkUwMEgoWytzyaXNYOuqVRHB++bSI2qmKegRxmKzrTA2uDWRun1ckGhJ2nt/wqALN+FuMutHKaeRCmqQpMGVHahES3LhFCYffM2MPIBhOZMYws0xv20qvxERpTAPnDD+BUBIH3yc/HEXpl9w0HEnHgZR2s06Dn1M6FjENkkITk9LTqlsZL15nFVvJRMwcSr/8A//9RhP/jcofzklbyeMrCMVQHKofe2uleU4pd5ohKazNjm3YOZhwjC4TpQ6TcQjSeuUor18opSc/j+v+uhX9gI7U50vxMQlJdFrprOlr0o2NROSZOHwtU2sRZGxD0WeqNdcXAPhzVPGVkqnAvP6SFgya8F8CofZbxyRbFGQBbgRVvKlqrrAREKRGWCepRgWugtdoujZeuSzFHrj/fRhuRHKYvQwyfIdMLsHghvn/W1fgrr3I5tgCsZ4VHcjwFYMSwFWV6AxxXSwu4akXDUn+vxYEw8H4feKYRiKGryKT9tkWPnQPNErARrdaIxZ3tgeN3KXtsgFYBz9KOHhvRFDLrhWHpf0lXzM8o+5s+kTEiKCvOERI1KkkrxmAVmxxgZSZ63OKVplw9zAG+Ov37O+8A75/Dqz1wgK9O//7OO8D75/BqDxzgq9O/v/MO8P45vNoDB/jq9O/vvAO8fw6v9sABvjr9+zvvAO+fw6s9cICvTv/+zjvA++fwag8c4KvTv7/zDvD+ObzaAwf46vTv7/xSAFu9KM7qaXm3t2Xs/vjkPRgZk64Aaz5iYuU8o4eRTVPWMtYBbo9AN4BrH/KrgV36cGlwtfSxc+RDhbl5a58DLNnJfHYwvhvkbEz9evwdmVeKQ/y5vYfO+ONPtTjkflaWiRsTm3Z8v76GAVwyVgKvVOG044Id8fjSokmTXkpu7bNxMUjxeMn+1E42DmkbldpR0l+yV7JHiqcFrDkdUwCWql4OHOnWjQBRmleq+nGVROaJba0BnPoUV9X4Wu7f0Qqci1tuXtbOWhxKi60HxMMAlipArjKG22muwkkgMdeRCszah1Y4dmGilRCt2D3sPAJgtJcr9Welni2Wlyp5rfdj+sJc/52rkNlb3OsronILuHZHSPUjvtbuFOldIae/1OLk5kYKRBq3rSpwD2Ndp0fgzx1L9SuHHkePwCIR6NYDL+Kfm3F4BBzgwxN8unvLA5xuINLNF5qgnB50bE6u1/ei3eZvSw6eBwWr9sAIcAxEiD4mmMzcqN6SjZq5dvAXjUtNbjmANYFHEqzRWwscMieaIMk2zVySTtS2EUdhrC0fR4IrVeDWoEuPdFsClY7VQJWbH/FZMxeil4mHxgZGv1Z2qQrcGvSdAGZ81cDD6Efg0diA6G2VWQZgpv+rJacUaKvNUWvAnxuP+EeyAYUaeFbyF3BRLbI0wJqKujPA4VGuRbviAKvXBD9QG+zWqq2pbLx3f0cw7zU8Rmvs1MbUwr+ROpapwFqn0UShclo7mHGxLSmcVnZa6WH8miHrAE+I+gOu3q2OAzwhsZop0UTV5FAdGvvYMVa27OIvG59UfusKzCSb3fmHQGn6z5akMD7V5tnF35ZYPU90VnqQwTjDbOA0R1cWJwGMP0F2NsCzFq4mVtsCzMJrAfCoxK4C8Ch/teC+F/xOFVi6LWrOjdkA9m4pHGAuI9u0EC3wShW49KHRUih7QjwC4JX85XD9K70FwK3w1gDWVO2dAV7N36MBlsB9OI/CpK1smn67JSlaO/8cL2Xet0BiNdrfllgtvYmT4EWS0Roc65MBxB4rgJG5SjIr2IDav2QLsRK8pfaj1wJaAZ4VbNgSYAlcpmVAA4DIjUzoyLm8AiPZJ2RqAPeqeIh5I6EaOZcDjGQflFkVXm8hfhM4s4DUEFqmB+69+9VWtt52WZ0eWOkZ7S9Y34piSwA8qvqyEM9IJmtjtTqRR2kz/D0eYI2DzLu2vfWzT71622OpX6PLeszyFVjjsOZpEzoPq3smwKX+HfV15d43+OAAM9kUNjPo7R85LmTMYheVlW5GTy/Z6wDWViVkF74iwD397QUlo/dKgN+3H+D7GRBwa/pmtxAxDEjlZ/xlQOsluwTAvZxzvedHwAE+P8dHe+gAH53e851zgM/P8dEeOsBHp/d85xzg83N8tIcO8NHpPd85B/j8HB/toQN8dHrPd257gB9Pl3o9Peqp+wS0VojPFIDjR5oSfIysNRQrJMjaJ0t9K8RnOMA1p3OwxvLp2CAfL4IgU7oWEpiOSf89fW8AXWgPuZKdjzmkeR8yUhyCTDwX6m8sh9hZigMbH8uF8/F+x+jvRisBnAtmCYYcsMGpNJEx0CXQay/AS+DG86ZAIfaXxqcJr8UnXhiov6mcJv5XV+C0ymgCGFeiFIQcAOm/5QD7WN1Ef10CIq1UKWzxfGGxaCow62/OXiQ+tTtirypb0zu9hUArRg5WFuDSq40jK3BpoUl3JnSBIwsZuVMgdl5ZgUs9Xq33y/WtaK+YVlSp4qF9aukWn1uQTB9Z6y1z12oQ1eRRO0sLJ23Z0riNqsbDK/Aox3yeOyLgAN+R52O9dICPTe0djjnAd+T5WC+XB7h2DGWVlRFz1DaTrX6gZ9W5jVdpg9xq06jxSwOcA8tyt1vSnzupsEwIMi8zHwowMi+qi7Gvp+ySAEuBbg2ypD8X8NY5Z1bg2f5eBTAS7BaYEP21gLfMXbuFtyTZv5mnJXpGYxmwWiBi5ulViVttSO1ygI0g1KphE6oFmOmpazZp59fGR2o/ao/C0UW4qr9SzJbogWtgWZ4QsLoY4KVAW11nfGBkH/at6K8UtyUBLr0z23LkwyZT6ldnVGHGB0ZWqvCWJz8SkOz15QBOwdAmIg2ElR42wJbyjA+MrKWNo3UtAzDTx2mq3+4JZe1n5UeDZzXfEgDXnLFKRE2P1RxWScnpYW3c3V80llcDjARJU+0RvayMBcDInKv4i9j63HiO/kwcalhtE6UJMntUl9qpmZP1tSTPwls7UUBtmukvaqMDzETqJTsjsTMADqGZ4S+TFq/ATLQm/GKlBl6LCuwAk2BY3j7RTVAtSas8meoBcOnDraUcrFyFr6/AmvcIRibUGuDV/WXr3tUAIyDOfLyqhbfUQqzuLwvv9Zs4JKEtMGgSEo+ZAfBMfzXx8goMRK0FJEB9VqR1zpbxLWO1/mrHOcBA5GYktHXOlvEtY4Fwmoo4wEA4ZyS0dc6W8S1jgXCaijjAQDhHJ9RivhYdLWOBcJqKOMBCOGecQlgApNUxw98Woq8BWLO7npFMLXjowxs/B25ZLoWxrS/apGqZd4s17jD60aO6YEdvgC391eiyHrNEBR4FcKkKM0FlK9hMgHv7y8Stl6wDTEa2J8CW1bdW0RmX2QXI6LaQvQ5gbVVCEtkKYOv4EhCaOxzirwWArTquBJipTkwiWwBsGYtCgIDM+IvO21NuCYB7Oui6z46AA3x2fo/3zgE+PsVnO+gAn53f471zgI9P8dkOOsBn5/d47xzg41N8toMO8Nn5Pd67f9vZ68My1rETAAAAAElFTkSuQmCC)
+
+## Usage
+
+In the main menu:
+
+* `Connect` - connect and start displaying respiration
+* `Vib` - Should we vibrate if the breaths per minute (BPM) is above a certain value?
+  * `No`   - don't vibrate
+  * `Calculated`   - vibrate if the app's reading is high. This is based on raw
+  sensor data and it responds quickly but may not be accurate.
+  * `Vernier`   - vibrate if the Vernier sensor's own reading is high. This is
+  more accurate but responds very slowly.
+* `Connect` - connect and start displaying respiration
+
+## TODO
+
+* Logging to a file?
+
+## Creator
+
+Gordon Williams
diff --git a/apps/vernierrespirate/app-icon.js b/apps/vernierrespirate/app-icon.js
new file mode 100644
index 000000000..f687d2a9d
--- /dev/null
+++ b/apps/vernierrespirate/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEw4UA///9nou30h/qJf8Ah/wBasK0ALhHcMBqALBgtABYsVqgLBAYILFqtUF4MVqoKEgoLFqALGEYQLFAwILEGAlV6tUlWoitXGAgLC1WqBYsBq+VqwLBAYPVMIUFBYN61Uq1oLBHgQLC1WohWqrwLDiteEIOggQDB2pICivqA4IFBlWq1YLCq/V9WkAoMa1YHBKQVf9XUAoMX1f1KgVVEYIpDEYILBLwIuBC4YFBMAMBLAILFLQILBrdV12UBYMW3VV8tAgt9q+2BYee6t9qEFuoLHroLBqte6wvDy+1ZoILBvdWSoeV9oLD+tfBYYFBBYTEBrq5CgN1aQNQgNVAALdEAAISBAYL1EfgISCAgIKDDAQSEAH4AQ"))
diff --git a/apps/vernierrespirate/app.js b/apps/vernierrespirate/app.js
new file mode 100644
index 000000000..945b72b77
--- /dev/null
+++ b/apps/vernierrespirate/app.js
@@ -0,0 +1,256 @@
+
+// get settings
+var settings = require("Storage").readJSON("vernierrespirate.json",1)||{};
+settings.vibrateBPM = settings.vibrateBPM||27;
+// settings.vibrate; // undefined / "calculated" / "vernier"
+
+function saveSettings() {
+  require("Storage").writeJSON("vernierrespirate.json", settings);
+}
+
+
+g.clear();
+var graphHeight = g.getHeight()-100;
+var last = {
+  time : Date.now(),
+  x : 0,
+  y : 24,
+};
+var avrValue;
+var aboveAvr = false;
+var lastBreath;
+var lastBreaths = [];
+var vibrateInterval;
+
+function onMsg(txt) {
+  print(txt);
+  E.showMessage(txt);
+}
+
+function setVibrate(isOn) {
+  var wasOn = vibrateInterval!==undefined;
+  if (isOn == wasOn) return;
+
+  if (isOn) {
+    vibrateInterval = setInterval(function() {
+      Bangle.buzz();
+    }, 1000);
+  } else {
+    clearInterval(vibrateInterval);
+    vibrateInterval = undefined;
+  }
+}
+
+function onBreath() {
+  var t = Date.now();
+  if (lastBreath!==undefined) {
+    // time between breaths
+    var value = 60000 / (t-lastBreath);
+    // average of last 3
+    while (lastBreaths.length>=3) lastBreaths.shift(); // keep length small
+    lastBreaths.push(value);
+    value = E.sum(lastBreaths) / lastBreaths.length;
+    // draw value
+    g.reset();
+    g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50);
+    g.setFont("6x8").setFontAlign(0,0);
+    g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95);
+    g.setFont("Vector",40).setFontAlign(0,0);
+    g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70);
+    // set vibration IF we're doing it from our calculations
+    if (settings.vibrate == "calculated")
+      setVibrate(value > settings.vibrateBPM);
+  }
+  lastBreath = t;
+}
+
+function onData(n, value) {
+  g.reset();
+  if (n==2) {
+    function scale(v) {
+      return Math.max(graphHeight - (1+v*4),24);
+    }
+    if (avrValue==undefined) avrValue=value;
+    avrValue = avrValue*0.95 + value*0.05;
+    if (avrValue < 1) avrValue = 1;
+    if (value > avrValue) {
+      if (!aboveAvr) onBreath();
+      aboveAvr = true;
+    } else aboveAvr = false;
+
+    var t = Date.now();
+    var x = Math.round((t - last.time) / 100) // 10 per second
+    if (last.x>=g.getWidth()) {
+      x = 0;
+      last.x = 0;
+      last.time = t;
+      g.clearRect(0,24,g.getWidth(),graphHeight);
+    }
+    var y = scale(value);
+    g.setPixel(x, scale(avrValue), "#f00");
+    g.drawLine(last.x, last.y, x, y);
+    last.x = x;
+    last.y = y;
+  }
+  if (n==4) {
+    g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight());
+    g.setFont("6x8").setFontAlign(0,0);
+    g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45);
+    g.setFont("Vector",40).setFontAlign(0,0);
+    g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20);
+    // set vibration IF we're doing it from our calculations
+    if (settings.vibrate == "vernier")
+      setVibrate(value > settings.vibrateBPM);
+  }
+  Bangle.setLCDPower(1); // ensure LCD is on
+}
+
+function connect() {
+  var gatt, service, rx, tx;
+  var rollingCounter = 0xFF;
+
+  // any button to exit
+  Bangle.setUI("updown", function() {
+    setVibrate(false);
+    Bangle.buzz();
+    try {
+      if (gatt) gatt.disconnect();
+    } catch (e) {
+    }
+    setTimeout(mainMenu, 1000);
+  });
+
+  function sendCommand(subCommand) {
+    const command = new Uint8Array(4 + subCommand.length);
+    command.set(new Uint8Array(subCommand), 4);
+    // Populate the packet header bytes
+    command[0] = 0x58; // header
+    command[1] = command.length;
+    command[2] = --rollingCounter;
+    command[3] = E.sum(command) & 0xFF; // checksum
+    return tx.writeValue(command);
+  }
+  function firstSetBit(v) {
+      return v & -v;
+  }
+  function handleResponse(dv) {
+    //print(dv.buffer);
+    var resType = dv.getUint8(0);
+    if (resType==0x20) {
+      // [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127])
+      // 6 = data type = real
+      // 6,0 = bit mask for sensors
+      // 2 = value count
+      if (dv.getUint8(4)!=6) return; //throw "Not float32 data";
+      var sensorIds = dv.getUint16(5, true);
+      // var count = dv.getUint8(7); doesn't seem right
+      var offs = 9;
+      while (sensorIds) {
+        var value = dv.getFloat32(offs, true);
+        var s = firstSetBit(sensorIds);
+        if (isFinite(value)) onData(s,value);
+        //else print(s,value);
+        sensorIds &= ~s;
+        offs += 4;
+      }
+    } else {
+      var cmd = dv.getUint8(4); // cmd
+      //print("CMD",dv.buffer);
+    }
+  }
+
+  onMsg("Searching...");
+  NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) {
+    device.on("gattserverdisconnected", function() {
+      onMsg("Device disconnected");
+    });
+    onMsg("Found. Connecting...");
+    return device.gatt.connect({minInterval:20, maxInterval:20});
+  }).then(function(g) {
+    gatt = g;
+    return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112");
+  }).then(function(s) {
+    service = s;
+    return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb");
+  }).then(function(c) {
+    tx = c;
+    return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe");
+  }).then(function(c) {
+    rx = c;
+    rx.on('characteristicvaluechanged', function(event) {
+      //print("EVT",event.target.value.buffer);
+      handleResponse(event.target.value);
+    });
+    return rx.startNotifications();
+  }).then(function() {
+    onMsg("Init");
+    sendCommand([ // init
+      0x1a,    0xa5,    0x4a,    0x06,
+      0x49,    0x07,    0x48,    0x08,
+      0x47,    0x09,    0x46,    0x0a,
+      0x45,    0x0b,    0x44,    0x0c,
+      0x43,    0x0d,    0x42,    0x0e,
+      0x41,
+    ]);
+    /*setTimeout(function() {
+      print("Set measurement period");
+      var us = 100000; // period in us
+      sendCommand([0x1b, 0xff, 0x00,
+        us & 255,
+        (us >> 8) & 255,
+        (us >> 16) & 255,
+        (us >> 24) & 255,
+        0x00,
+        0x00,
+        0x00,
+        0x00]);
+    }, 100);*/
+
+  /*  setTimeout(function() {
+      print("Get sensor info");
+      sendCommand([0x51, 0]); // get sensor IDs
+      // returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0]
+      // 54 is the bit mask of available channels
+      //sendCommand([106, 16]); // get sensor info
+    }, 2000);*/
+
+    setTimeout(function() {
+      onMsg("Start measurements");
+      //https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588
+      var channels = 6; // data channels 4 and 2
+      sendCommand([ // start measurements
+      0x18,    0xff,    0x01,    channels,
+      0x00,    0x00,    0x00,    0x00,
+      0x00,    0x00,    0x00,    0x00,
+      0x00,    0x00,    0x00
+      ]);
+    }, 500);
+  }).catch(function() {
+    onMsg("Connect Fail");
+  });
+}
+
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+
+function mainMenu() {
+  var vibText = ["No","Calculated","Vernier"];
+  var vibValue = ["","calculated","vernier"];
+  E.showMenu({"":{title:"Respiration Belt"},
+    "< Back" : () => { saveSettings(); load(); },
+    "Connect" : () => { saveSettings(); E.showMenu(); connect(); },
+    "Vib" : {
+      value : Math.max(vibValue.indexOf(settings.vibrate),0),
+      format : v => vibText[v],
+      min:0,max:2,
+      onchange : v => { settings.vibrate=vibValue[v]; }
+    },
+    "BPM" : {
+      value : settings.vibrateBPM,
+      min:10,max:50,
+      onchange : v => { settings.vibrateBPM=v; }
+    }
+  });
+}
+
+mainMenu();
diff --git a/apps/vernierrespirate/app.png b/apps/vernierrespirate/app.png
new file mode 100644
index 000000000..0f6b22af1
Binary files /dev/null and b/apps/vernierrespirate/app.png differ
diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog
index a5fdc31cc..5986ecf3f 100644
--- a/apps/widbat/ChangeLog
+++ b/apps/widbat/ChangeLog
@@ -5,3 +5,4 @@
 0.06: Use 'g.theme' (requires bootloader 0.23)
 0.07: Move CHARGING variable to more readable string
 0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on
+0.09: Misc speed/memory tweaks
diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js
index 739326df0..a8a0c5382 100644
--- a/apps/widbat/widget.js
+++ b/apps/widbat/widget.js
@@ -1,26 +1,11 @@
 (function(){
-
   function setWidth() {
     WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
   }
-  function draw() {
-    var s = 39;
-    var x = this.x, y = this.y;
-    g.reset();
-    if (Bangle.isCharging()) {
-      g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
-      x+=16;
-    }
-    g.setColor(g.theme.fg);
-    g.fillRect(x,y+2,x+s-4,y+21);
-    g.clearRect(x+2,y+4,x+s-6,y+19);
-    g.fillRect(x+s-3,y+10,x+s,y+14);
-    g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);
-  }
   Bangle.on('charging',function(charging) {
     if(charging) Bangle.buzz();
     setWidth();
-    Bangle.drawWidgets(); // relayout widgets
+    Bangle.drawWidgets(); // re-layout widgets
     g.flip();
   });
   var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined;
@@ -37,6 +22,16 @@
       }
     }
   });
-  WIDGETS["bat"]={area:"tr",width:40,draw:draw};
+  WIDGETS["bat"]={area:"tr",width:40,draw:function() {
+    var s = 39;
+    var x = this.x, y = this.y;
+    g.reset();
+    if (Bangle.isCharging()) {
+      g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
+      x+=16;
+    }
+    g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14);
+    g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);
+  }};
   setWidth();
 })()
diff --git a/apps/widbat/widget.png b/apps/widbat/widget.png
index 630692e38..4f7491ee9 100644
Binary files a/apps/widbat/widget.png and b/apps/widbat/widget.png differ
diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog
new file mode 100644
index 000000000..55cda0f21
--- /dev/null
+++ b/apps/widbatv/ChangeLog
@@ -0,0 +1 @@
+0.01: New widget
diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js
new file mode 100644
index 000000000..cc52a0f8e
--- /dev/null
+++ b/apps/widbatv/widget.js
@@ -0,0 +1,19 @@
+Bangle.on('charging',function(charging) {
+  if(charging) Bangle.buzz();
+  WIDGETS["batv"].draw();
+});
+setInterval(()=>WIDGETS["batv"].draw(), 60000);
+Bangle.on('lcdPower', function(on) {
+  if (on) WIDGETS["batv"].draw(); // refresh at power on
+});
+WIDGETS["batv"]={area:"tr",width:14,draw:function() {
+  var x = this.x, y = this.y;
+  g.reset();
+  if (Bangle.isCharging()) {
+    g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
+  } else {
+    g.clearRect(x,y,x+14,y+24);
+    g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2);
+    g.setColor("#0f0").fillRect(x+4,y+20-(E.getBattery()*16/100),x+10,y+20);
+  }
+}};
diff --git a/apps/widbatv/widget.png b/apps/widbatv/widget.png
new file mode 100644
index 000000000..e31704d7b
Binary files /dev/null and b/apps/widbatv/widget.png differ
diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog
index 4509487ac..7aa96ce5c 100644
--- a/apps/widbt/ChangeLog
+++ b/apps/widbt/ChangeLog
@@ -3,3 +3,4 @@
 0.04: Fix automatic update of Bluetooth connection status
 0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color
 0.06: Tweaking colors for dark/light themes and low bpp screens
+0.07: Memory usage improvements
diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js
index a25d2c21c..88be3d5c9 100644
--- a/apps/widbt/widget.js
+++ b/apps/widbt/widget.js
@@ -1,17 +1,13 @@
-(function(){
-  function draw() {
-    g.reset();
-    if (NRF.getSecurityStatus().connected)
-      g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
-    else
-      g.setColor(g.theme.dark ? "#666" : "#999");
-    g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y);
-  }
-  function changed() {
-    WIDGETS["bluetooth"].draw();
-    g.flip();// turns screen on
-  }
-  NRF.on('connect',changed);
-  NRF.on('disconnect',changed);
-  WIDGETS["bluetooth"]={area:"tr",width:15,draw:draw};
-})()
+WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() {
+  g.reset();
+  if (NRF.getSecurityStatus().connected)
+    g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f"));
+  else
+    g.setColor(g.theme.dark ? "#666" : "#999");
+  g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y);
+},changed:function() {
+  WIDGETS["bluetooth"].draw();
+  Bangle.setLCDPower(1); // turn screen on
+}};
+NRF.on('connect',WIDGETS["bluetooth"].changed);
+NRF.on('disconnect',WIDGETS["bluetooth"].changed);
diff --git a/apps/widcom/ChangeLog b/apps/widcom/ChangeLog
new file mode 100644
index 000000000..5d08e91e6
--- /dev/null
+++ b/apps/widcom/ChangeLog
@@ -0,0 +1,3 @@
+0.02: Works with light theme
+      Doesn't drain battery by updating every 2 secs
+      Fix alignment
diff --git a/apps/widcom/widget.js b/apps/widcom/widget.js
index b9c911dbf..bce9453c5 100644
--- a/apps/widcom/widget.js
+++ b/apps/widcom/widget.js
@@ -1,30 +1,17 @@
 (function(){
-  //var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA"));
-  //var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA=="));
-  var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"));        
-  
-  function draw() {
+  var cp = Bangle.setCompassPower;
+  Bangle.setCompassPower = () => {
+    cp.apply(Bangle, arguments);
+    WIDGETS.compass.draw();
+  };
+
+  WIDGETS.compass={area:"tr",width:24,draw:function() {
     g.reset();
     if (Bangle.isCompassOn()) {
-      g.setColor(1,0.8,0);     // on = amber
+      g.setColor(g.theme.dark ? "#FC0" : "#F00");
     } else {
-      g.setColor(0.3,0.3,0.3); // off = grey
+      g.setColor(g.theme.dark ? "#333" : "#CCC");
     }
-    g.drawImage(img, 10+this.x, 2+this.var);
-  }
-
-  var timerInterval;
-  Bangle.on('lcdPower', function(on) {
-    if (on) {
-      WIDGETS.compass.draw();
-      if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000);
-    } else {
-      if (timerInterval) {
-        clearInterval(timerInterval);
-        timerInterval = undefined;
-      }
-    }
-  });
-
-  WIDGETS.compass={area:"tr",width:24,draw:draw};
+    g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var);
+  }};
 })();
diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog
index 45dcfa87e..93e2eaf66 100644
--- a/apps/widhrm/ChangeLog
+++ b/apps/widhrm/ChangeLog
@@ -2,3 +2,4 @@
 0.02: Tweaks for variable size widget system
 0.03: Ensure redrawing works with variable size widget system
 0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
+0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2)
diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js
index 54b105d1e..7ffe1aa6d 100644
--- a/apps/widhrm/widget.js
+++ b/apps/widhrm/widget.js
@@ -1,13 +1,38 @@
 (() => {
-  var currentBPM = undefined;
-  var lastBPM = undefined;
-  var firstBPM = true; // first reading since sensor turned on
+  if (!Bangle.isLocked) return; // old firmware
+  var currentBPM;
+  var lastBPM;
+  var isHRMOn = false;
 
-  function draw() {
+  // turn on sensor when the LCD is unlocked
+  Bangle.on('lock', function(isLocked) {
+    if (!isLocked) {
+      Bangle.setHRMPower(1,"widhrm");
+      currentBPM = undefined;
+      WIDGETS["hrm"].draw();
+    } else {
+      Bangle.setHRMPower(0,"widhrm");
+    }
+  });
+
+  var hp = Bangle.setHRMPower;
+  Bangle.setHRMPower = () => {
+    hp.apply(Bangle, arguments);
+    isHRMOn = Bangle.isHRMOn();
+    WIDGETS["hrm"].draw();
+  };
+
+  Bangle.on('HRM',function(d) {
+    currentBPM = d.bpm;
+    lastBPM = currentBPM;
+    WIDGETS["hrm"].draw();
+  });
+
+  // add your widget
+  WIDGETS["hrm"]={area:"tl",width:24,draw:function() {
     var width = 24;
     g.reset();
-    g.setFont("6x8", 1);
-    g.setFontAlign(0, 0);
+    g.setFont("6x8", 1).setFontAlign(0, 0);
     g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background
     var bpm = currentBPM, isCurrent = true;
     if (bpm===undefined) {
@@ -16,36 +41,12 @@
     }
     if (bpm===undefined)
       bpm = "--";
-    g.setColor(isCurrent ? "#ffffff" : "#808080");
+    g.setColor(isCurrent ? g.theme.fg : "#808080");
     g.drawString(bpm, this.x+width/2, this.y+19);
-    g.setColor(isCurrent ? "#ff0033" : "#808080");
+    g.setColor(isHRMOn ? "#ff0033" : "#808080");
     g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1);
     g.setColor(-1);
-  }
+  }};
 
-  // redraw when the LCD turns on
-  Bangle.on('lcdPower', function(on) {
-    if (on) {
-      Bangle.setHRMPower(1,"widhrm");
-      firstBPM = true;
-      currentBPM = undefined;
-      WIDGETS["hrm"].draw();
-    } else {
-      Bangle.setHRMPower(0,"widhrm");
-    }
-  });
-
-  Bangle.on('HRM',function(d) {
-    if (firstBPM)
-      firstBPM=false; // ignore the first one as it's usually rubbish
-    else {
-      currentBPM = d.bpm;
-      lastBPM = currentBPM;
-    }
-    WIDGETS["hrm"].draw();
-  });
-  Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm");
-
-  // add your widget
-  WIDGETS["hrm"]={area:"tl",width:24,draw:draw};
+  Bangle.setHRMPower(!Bangle.isLocked(),"widhrm");
 })();
diff --git a/apps/widhrt/ChangeLog b/apps/widhrt/ChangeLog
index fdb495797..39520ad6a 100644
--- a/apps/widhrt/ChangeLog
+++ b/apps/widhrt/ChangeLog
@@ -1,3 +1,5 @@
 0.01: First version
 0.02: Don't break if running on 2v08 firmware (just don't display anything)
-
+0.03: Works with light theme
+      Doesn't drain battery by updating every 2 secs
+      fix alignment
diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js
index 8ac76def8..d9716fa24 100644
--- a/apps/widhrt/widget.js
+++ b/apps/widhrt/widget.js
@@ -1,28 +1,18 @@
 (function(){
   if (!Bangle.isHRMOn) return; // old firmware
+  var hp = Bangle.setHRMPower;
+  Bangle.setHRMPower = () => {
+    hp.apply(Bangle, arguments);
+    WIDGETS.widhrt.draw();
+  };
 
-  function draw() {
+  WIDGETS.widhrt={area:"tr",width:24,draw:function() {
     g.reset();
     if (Bangle.isHRMOn()) {
-      g.setColor(1,0,0);     // on = red
+      g.setColor("#f00");     // on = red
     } else {
-      g.setColor(0.3,0.3,0.3); // off = grey
+      g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
     }
-    g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y);
-  }
-
-  var timerInterval;
-  Bangle.on('lcdPower', function(on) {
-    if (on) {
-      WIDGETS.widhrt.draw();
-      if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000);
-    } else {
-      if (timerInterval) {
-        clearInterval(timerInterval);
-        timerInterval = undefined;
-      }
-    }
-  });
-
-  WIDGETS.widhrt={area:"tr",width:24,draw:draw};
+    g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y);
+  }};
 })();
diff --git a/apps/widtbat/ChangeLog b/apps/widtbat/ChangeLog
index 4c21f3ace..37513808a 100644
--- a/apps/widtbat/ChangeLog
+++ b/apps/widtbat/ChangeLog
@@ -1 +1,2 @@
 0.01: New Widget!
+0.02: Theme support, memory savings
diff --git a/apps/widtbat/widget.js b/apps/widtbat/widget.js
index 6d5aded8b..c78653358 100644
--- a/apps/widtbat/widget.js
+++ b/apps/widtbat/widget.js
@@ -1,17 +1,11 @@
-/* jshint esversion: 6 */
-(() => {
-  const CBS = 0x41f, CBC = 0x07E0;
-  var xo = 6, xl = 22, yo = 9, h = 17;
-
-  function draw() {
-    g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4);
-    g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h);
-    var cbc = (Bangle.isCharging()) ? CBC : CBS;
-    g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h);
-  }
-  Bangle.on('charging', function(charging) {
-    if (charging) Bangle.buzz();
-    Bangle.drawWidgets();
-  });
-  WIDGETS["widtbat"] = { area:"tr", width:32, draw: draw };
-})();
+Bangle.on('charging', function(charging) {
+  if (charging) Bangle.buzz();
+  WIDGETS["widtbat"].draw();
+});
+WIDGETS["widtbat"] = { area:"tr", width:32, draw: function() {
+  const xo = 6, xl = 22, yo = 9, h = 17;
+  g.reset().setColor("#08f").drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4);
+  g.clearRect(this.x + xo, this.y + yo, this.x + xl, this.y + h);
+  var cbc = (Bangle.isCharging()) ? "#0f0" : "#08f";
+  g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h);
+} };
diff --git a/apps/widtbat/widget.png b/apps/widtbat/widget.png
index 4294f0ca3..f2943bc52 100644
Binary files a/apps/widtbat/widget.png and b/apps/widtbat/widget.png differ
diff --git a/apps/worldclock/ChangeLog b/apps/worldclock/ChangeLog
index e922ef2a4..831dd3b5c 100644
--- a/apps/worldclock/ChangeLog
+++ b/apps/worldclock/ChangeLog
@@ -2,3 +2,5 @@
 0.02: Update custom.html for refactor; add README
 0.03: Update for larger secondary timezone display (#610)
 0.04: setUI, different screen sizes
+0.05: Now update *on* the minute rather than every 15 secs
+      Fix rendering of single extra timezone on Bangle.js 2
diff --git a/apps/worldclock/app.js b/apps/worldclock/app.js
index 84cb29874..2627e056c 100644
--- a/apps/worldclock/app.js
+++ b/apps/worldclock/app.js
@@ -1,5 +1,3 @@
-/* jshint esversion: 6 */
-
 const big = g.getWidth()>200;
 // Font for primary time and date
 const primaryTimeFontSize = big?6:5;
@@ -16,8 +14,13 @@ const xcol2 = g.getWidth() - xcol1;
 
 const font = "6x8";
 
+/* TODO: we could totally use 'Layout' here and
+avoid a whole bunch of hard-coded offsets */
+
+
 const xyCenter = g.getWidth() / 2;
 const yposTime = big ? 75 : 60;
+const yposTime2 = yposTime + (big ? 100 : 60);
 const yposDate = big ? 130 : 90;
 const yposWorld = big ? 170 : 120;
 
@@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || [];
 // TESTING CODE
 // Used to test offset array values during development.
 // Uncomment to override secondary offsets value
-
-// const mockOffsets = {
-//   zeroOffsets: [],
-//   oneOffset: [["UTC", 0]],
-//   twoOffsets: [
-//     ["Tokyo", 9],
-//     ["UTC", 0],
-//   ],
-//   fourOffsets: [
-//     ["Tokyo", 9],
-//     ["UTC", 0],
-//     ["Denver", -7],
-//     ["Miami", -5],
-//   ],
-//   fiveOffsets: [
-//     ["Tokyo", 9],
-//     ["UTC", 0],
-//     ["Denver", -7],
-//     ["Chicago", -6],
-//     ["Miami", -5],
-//   ],
-// };
+/*
+const mockOffsets = {
+ zeroOffsets: [],
+ oneOffset: [["UTC", 0]],
+ twoOffsets: [
+   ["Tokyo", 9],
+   ["UTC", 0],
+ ],
+ fourOffsets: [
+   ["Tokyo", 9],
+   ["UTC", 0],
+   ["Denver", -7],
+   ["Miami", -5],
+ ],
+ fiveOffsets: [
+   ["Tokyo", 9],
+   ["UTC", 0],
+   ["Denver", -7],
+   ["Chicago", -6],
+   ["Miami", -5],
+   ],
+};*/
 
 // Uncomment one at a time to test various offsets array scenarios
-// offsets = mockOffsets.zeroOffsets; // should render nothing below primary time
-// offsets = mockOffsets.oneOffset; // should render larger in two rows
-// offsets = mockOffsets.twoOffsets; // should render two in columns
-// offsets = mockOffsets.fourOffsets; // should render in columns
-// offsets = mockOffsets.fiveOffsets; // should render first four in columns
+//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time
+//offsets = mockOffsets.oneOffset; // should render larger in two rows
+//offsets = mockOffsets.twoOffsets; // should render two in columns
+//offsets = mockOffsets.fourOffsets; // should render in columns
+//offsets = mockOffsets.fiveOffsets; // should render first four in columns
 
 // END TESTING CODE
 
 // Check settings for what type our clock should be
 //var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
-var secondInterval;
+
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+  if (drawTimeout) clearTimeout(drawTimeout);
+  drawTimeout = setTimeout(function() {
+    drawTimeout = undefined;
+    draw();
+  }, 60000 - (Date.now() % 60000));
+}
 
 function doublenum(x) {
   return x < 10 ? "0" + x : "" + x;
@@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) {
   return new Date(dt.getTime() + offset * 60 * 60 * 1000);
 }
 
-function drawSimpleClock() {
+function draw() {
   // get date
   var d = new Date();
   var da = d.toString().split(" ");
@@ -111,9 +125,9 @@ function drawSimpleClock() {
       // For a single secondary timezone, draw it bigger and drop time zone to second line
       const xOffset = 30;
       g.setFont(font, secondaryTimeFontSize);
-      g.drawString(`${hours}:${minutes}`, xyCenter, yposTime + 100, true);
+      g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
       g.setFont(font, secondaryTimeZoneFontSize);
-      g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true);
+      g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
 
       // draw Day, name of month, Date
       g.setFont(font, secondaryTimeZoneFontSize);
@@ -132,6 +146,8 @@ function drawSimpleClock() {
       g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
     }
   });
+
+  queueDraw();
 }
 
 // clean app screen
@@ -141,18 +157,15 @@ Bangle.setUI("clock");
 Bangle.loadWidgets();
 Bangle.drawWidgets();
 
-// refesh every 15 sec when screen is on
-Bangle.on("lcdPower", (on) => {
-  if (secondInterval) clearInterval(secondInterval);
-  secondInterval = undefined;
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
   if (on) {
-    secondInterval = setInterval(drawSimpleClock, 15e3);
-    drawSimpleClock(); // draw immediately
+    draw(); // draw immediately, queue redraw
+  } else { // stop draw timer
+    if (drawTimeout) clearTimeout(drawTimeout);
+    drawTimeout = undefined;
   }
 });
 
-// draw now and every 15 sec until display goes off
-drawSimpleClock();
-if (Bangle.isLCDOn()) {
-  secondInterval = setInterval(drawSimpleClock, 15e3);
-}
+// draw now
+draw();
diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js
new file mode 100644
index 000000000..6908591a5
--- /dev/null
+++ b/bin/create_app_supports_field.js
@@ -0,0 +1,83 @@
+#!/usr/bin/nodejs
+/* Quick hack to add proper 'supports' field to apps.json
+*/
+
+var fs = require("fs");
+
+var BASEDIR = __dirname+"/../";
+
+var appsFile, apps;
+try {
+  appsFile = fs.readFileSync(BASEDIR+"apps.json").toString();
+} catch (e) {
+  ERROR("apps.json not found");
+}
+try{
+  apps = JSON.parse(appsFile);
+} catch (e) {
+  console.log(e);
+  var m = e.toString().match(/in JSON at position (\d+)/);
+  if (m) {
+    var char = parseInt(m[1]);
+    console.log("===============================================");
+    console.log("LINE "+appsFile.substr(0,char).split("\n").length);
+    console.log("===============================================");
+    console.log(appsFile.substr(char-10, 20));
+    console.log("===============================================");
+  }
+  console.log(m);
+  ERROR("apps.json not valid JSON");
+
+}
+
+apps = apps.map((app,appIdx) => {
+  var tags = [];
+  if (app.tags) tags = app.tags.split(",").map(t=>t.trim());
+  var supportsB1 = true;
+  var supportsB2 = false;
+  if (tags.includes("b2")) {
+    tags = tags.filter(x=>x!="b2");
+    supportsB2 = true;
+  }
+  if (tags.includes("bno2")) {
+    tags = tags.filter(x=>x!="bno2");
+    supportsB2 = false;
+  }
+  if (tags.includes("bno1")) {
+    tags = tags.filter(x=>x!="bno1");
+    supportsB1 = false;
+  }
+  app.tags = tags.join(",");
+  app.supports = [];
+  if (supportsB1) app.supports.push("BANGLEJS");
+  if (supportsB2) app.supports.push("BANGLEJS2");
+  return app;
+});
+
+var KEY_ORDER = [
+  "id","name","shortName","version","description","icon","type","tags","supports",
+  "dependencies", "readme", "custom", "customConnect", "interface",
+  "allow_emulator", "storage", "data", "sortorder"
+];
+
+var JS = JSON.stringify;
+var json = "[\n  "+apps.map(app=>{
+  var keys = KEY_ORDER.filter(k=>k in app);
+  Object.keys(app).forEach(k=>{
+    if (!KEY_ORDER.includes(k))
+      throw new Error(`Key named ${k} not known!`);
+  });
+
+
+  return "{\n    "+keys.map(k=>{
+    var js = JS(app[k]);
+    if (k=="storage")
+      js = "[\n      "+app.storage.map(s=>JS(s)).join(",\n      ")+"\n    ]";
+    return JS(k)+": "+js;
+  }).join(",\n    ")+"\n  }";
+}).join(",\n  ")+"\n]\n";
+
+//console.log(json);
+
+console.log("new apps.json written");
+fs.writeFileSync(BASEDIR+"apps.json", json);
diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js
index b98aa9ef3..dbce9c855 100755
--- a/bin/sanitycheck.js
+++ b/bin/sanitycheck.js
@@ -51,7 +51,8 @@ try{
 
 const APP_KEYS = [
   'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
-  'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator',
+  'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data',
+  'supports', 'allow_emulator', 
   'dependencies'
 ];
 const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
@@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => {
   if (!app.name) ERROR(`App ${app.id} has no name`);
   var isApp = !app.type || app.type=="app";
   if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
+  if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
+  else {
+    app.supports.forEach(dev => {
+      if (!["BANGLEJS","BANGLEJS2"].includes(dev))
+        ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`);
+    });
+  }
+
   if (!app.version) WARN(`App ${app.id} has no version`);
   else {
     if (!fs.existsSync(appDir+"ChangeLog")) {
diff --git a/core b/core
index 0fd608f08..3a2c706b4 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba
+Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a
diff --git a/css/main.css b/css/main.css
index 0dbe8da14..44137e9b1 100644
--- a/css/main.css
+++ b/css/main.css
@@ -23,6 +23,9 @@
 .filter-nav {
   display: inline-block;
 }
+.device-nav {
+  display: inline-block;
+}
 .sort-nav {
   float: right;
 }
@@ -32,6 +35,21 @@
   top: 36px;
   left: -24px;
 }
-.btn-favourite {
-  color: red;
+.btn.btn-favourite { color: red; }
+.btn.btn-favourite:hover { color: red; }
+
+.icon.icon-emulator { text-indent: 0px; } /*override spectre*/
+.icon.icon-emulator::before {
+  content: "\01F5B5";
+}
+.icon.icon-favourite { text-indent: 0px; } /*override spectre*/
+.icon.icon-favourite::before {
+  content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */
+}
+.icon.icon-favourite-active::before {
+  content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star  */
+}
+.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/
+.icon.icon-interface::before {
+  content: "\01F5AB";
 }
diff --git a/css/spectre-exp.min.css b/css/spectre-exp.min.css
index 942cf59bf..d3137743a 100644
--- a/css/spectre-exp.min.css
+++ b/css/spectre-exp.min.css
@@ -1 +1 @@
-/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:flex;display:-ms-flexbox;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:flex;display:-ms-flexbox;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:flex;display:-ms-flexbox;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:flex;display:-ms-flexbox;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:flex;display:-ms-flexbox;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1}
\ No newline at end of file
+/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1}
\ No newline at end of file
diff --git a/css/spectre-icons.min.css b/css/spectre-icons.min.css
index 9b6167caa..0276f7b84 100644
--- a/css/spectre-icons.min.css
+++ b/css/spectre-icons.min.css
@@ -1 +1 @@
-/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em}
\ No newline at end of file
+/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em}
\ No newline at end of file
diff --git a/css/spectre.min.css b/css/spectre.min.css
index 8df0bf64f..0fe23d9c0 100644
--- a/css/spectre.min.css
+++ b/css/spectre.min.css
@@ -1 +1 @@
-/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:inline-flex;display:-ms-inline-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:flex;display:-ms-flexbox}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:flex;display:-ms-flexbox}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:inline-flex;display:-ms-inline-flexbox}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.columns.col-gapless{margin-left:0;margin-right:0}.columns.col-gapless>.column{padding-left:0;padding-right:0}.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:flex;display:-ms-flexbox;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:inline-flex;display:-ms-inline-flexbox;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:flex;display:-ms-flexbox;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:flex;display:-ms-flexbox;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:flex;display:-ms-flexbox;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:flex;display:-ms-flexbox}.d-inline-flex{display:inline-flex;display:-ms-inline-flexbox}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:sticky!important;position:-webkit-sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word}
\ No newline at end of file
+/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word}
\ No newline at end of file
diff --git a/defaultapps.json b/defaultapps_banglejs1.json
similarity index 100%
rename from defaultapps.json
rename to defaultapps_banglejs1.json
diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json
new file mode 100644
index 000000000..2d32d285c
--- /dev/null
+++ b/defaultapps_banglejs2.json
@@ -0,0 +1 @@
+["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"]
diff --git a/index.html b/index.html
index a5ae7bff0..0185f1bae 100644
--- a/index.html
+++ b/index.html
@@ -60,6 +60,17 @@
 
     
+
diff --git a/loader.js b/loader.js index 6528ffc98..45ec87df3 100644 --- a/loader.js +++ b/loader.js @@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") { var RECOMMENDED_VERSION = "2v10"; // could check http://www.espruino.com/json/BANGLEJS.json for this +// We're only interested in Bangles +DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); + +// Set up source code URL (function() { let username = "espruino"; let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); @@ -21,6 +25,7 @@ var RECOMMENDED_VERSION = "2v10"; Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; })(); +// When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); @@ -33,4 +38,126 @@ function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId == "BANGLEJS2") { Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + + // check against features shown? + filterAppsForDevice(deviceId); + /* if we'd saved a device ID but this device is different, ensure + we ask again next time */ + var savedDeviceId = getSavedDeviceId(); + if (savedDeviceId!==undefined && savedDeviceId!=deviceId) + setSavedDeviceId(undefined); } + +var originalAppJSON = undefined; +function filterAppsForDevice(deviceId) { + if (originalAppJSON===undefined) + originalAppJSON = appJSON; + + var device = DEVICEINFO.find(d=>d.id==deviceId); + // set the device dropdown + document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps"; + + if (!device) { + if (deviceId!==undefined) + showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning"); + appJSON = originalAppJSON; + } else { + // Now filter apps + appJSON = originalAppJSON.filter(app => { + var supported = ["BANGLEJS"]; + if (!app.supports) { + console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`); + return false; + } + if (app.supports.includes(deviceId)) return true; + //console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`); + return false; + }); + } + refreshLibrary(); +} + +// If 'remember' was checked in the window below, this is the device +function getSavedDeviceId() { + let deviceId = localStorage.getItem("deviceId"); + if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId)) + return deviceId; + return undefined; +} + +function setSavedDeviceId(deviceId) { + localStorage.setItem("deviceId", deviceId); +} + +// At boot, show a window to choose which type of device you have... +window.addEventListener('load', (event) => { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) + return filterAppsForDevice(deviceId); + + var html = `
+ ${DEVICEINFO.map(d=>` +
+
+
+
${d.name}
+ +
+
+ ${d.name} +
+
+
`).join("\n")} +
+
+
+ +
+
+
`; + showPrompt("Which Bangle.js?",html,{},false); + htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { + button.addEventListener("click",event => { + let rememberDevice = document.getElementById("remember_device").checked; + + let button = event.currentTarget; + let deviceId = button.getAttribute("deviceid"); + hidePrompt(); + console.log("Chosen device", deviceId); + setSavedDeviceId(rememberDevice ? deviceId : undefined); + filterAppsForDevice(deviceId); + }); + }); +}); + +window.addEventListener('load', (event) => { + // Hook onto device chooser dropdown + htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { + button.addEventListener("click", event => { + var a = event.target; + var deviceId = a.getAttribute("dt")||undefined; + filterAppsForDevice(deviceId); // also sets the device dropdown + setSavedDeviceId(undefined); // ask at startup next time + document.querySelector(".devicetype-nav span").innerText = a.innerText; + }); + }); + + // Button to install all default apps in one go + document.getElementById("installdefault").addEventListener("click",event=>{ + getInstalledApps().then(() => { + if (device.id == "BANGLEJS") + return httpGet("defaultapps_banglejs.json"); + if (device.id == "BANGLEJS2") + return httpGet("defaultapps_banglejs2.json"); + throw new Error("Unknown device "+device.id); + }).then(json=>{ + return installMultipleApps(JSON.parse(json), "default"); + }).catch(err=>{ + Progress.hide({sticky:true}); + showToast("App Install failed, "+err,"error"); + }); + }); +}); diff --git a/modules/Layout.js b/modules/Layout.js index 03aa6249b..319f6901e 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -169,14 +169,23 @@ function Layout(layout, options) { Bangle.on('touch',Bangle.touchHandler); } - // add IDs + // recurse over layout doing some fixing up if needed var ll = this; - function idRecurser(l) { + function recurser(l) { + // add IDs if (l.id) ll[l.id] = l; + // fix type up if (!l.type) l.type=""; - if (l.c) l.c.forEach(idRecurser); + // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally + // fix fonts for pre-2v11 firmware + if (l.font && l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + if (l.c) l.c.forEach(recurser); } - idRecurser(layout); + recurser(this._l); this.updateNeeded = true; } @@ -352,12 +361,6 @@ Layout.prototype.update = function() { "txt" : function(l) { if (l.font.endsWith("%")) l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } if (l.wrap) { l._h = l._w = 0; } else {