Repository: almarklein/timetagger (language: python)

Issue:
Redesign
Just an issue to track the redesign that we're working on.


Discussion / hints:

Fix patch:
diff --git a/requirements.txt b/requirements.txt
index 8a24c445..82a7cee5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
 jinja2
 markdown
+pyScss
 asgineer
 itemdb
 pscript
diff --git a/timetagger/app/_template.html b/timetagger/app/_template.html
index 56d1c549..6e22b224 100644
--- a/timetagger/app/_template.html
+++ b/timetagger/app/_template.html
@@ -5,16 +5,23 @@
   <title>{{ title }}</title>
   <meta name="description" content="{{ description }}">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <meta name="theme-color" content="#fff">
+  <meta name="theme-color" content="#F4F4F4">
   <link rel="manifest" href="timetagger_manifest.json">
-  <link rel="icon" sizes="192x192" href="timetagger192filled.png">
-  <link rel="apple-touch-icon" href="timetagger192noalpha.png">
+  <link rel="icon" sizes="192x192" href="timetagger192_sf.png">
+  <link rel="apple-touch-icon" href="timetagger192_sf.png">
 
   <style>
   {{ embedded_style }}
   </style>
+  <link rel="stylesheet" href="app.css">
 
-  <link rel="stylesheet" href="style.css">
+  <script src='utils.js'></script>
+  <script src='dt.js'></script>
+  <script src='stores.js'></script>
+  <script src='dialogs.js'></script>
+  <script src='front.js'></script>
+  <script src='jspdf.js'></script>
+  <script src='Ubuntu-C-normal.js'></script>
 
   <script>
   window.timetaggerversion = '{{ versionstring }}';
@@ -26,11 +33,11 @@
       ) {
       window.browser_supported = true;  // probably
   }
-  // Update menu content
+  // Disable canvas if browser cannot run the app
   window.addEventListener("load", function() {
-    var menu = document.getElementById("header-content");
+    var el = document.getElementById("main-content");
     if (!window.browser_supported) {
-        menu.innerHTML = "This browser is not supported, please use (a modern version of) Firefox, Chrome, Safari, or Edge.";
+        el.innerHTML = "This browser is not supported, please use (a modern version of) Firefox, Chrome, Safari, or Edge.";
     }
   });
   </script>
@@ -42,6 +49,17 @@
 <main>
     <div class='content' id='main-content'>
         {{ main }}
+
+        <!-- Force preloading the font used in the canvas -->
+        <span class='normalfont'>tt</span>
+        <span class='narrowfont'>tt</span>
+        <span class='monofont'>tt</span>
+        <span class='fas' style='color: #eee;'></span>
+
+        <!-- Load images that we need an img element for -->
+        <img id='ttlogo_bd' alt='TimeTagger logo' src='timetagger192_bd.png' width='2px' />
+        <img id='ttlogo_sl' alt='TimeTagger logo' src='timetagger192_sl.png' width='2px' />
+        <img id='ttlogo_tg' alt='TimeTagger logo' src='timetagger192_tg.png' width='2px' />
     </div>
 </main>
 
diff --git a/timetagger/common/style.css b/timetagger/app/app.scss
similarity index 71%
rename from timetagger/common/style.css
rename to timetagger/app/app.scss
index 73fad63a..983037e3 100644
--- a/timetagger/common/style.css
+++ b/timetagger/app/app.scss
@@ -1,156 +1,27 @@
-/**************** Main layout ****************/
+body { background: $bg1; }
+body.darkmode { background: $bg3; }
 
-html {
-    height: 100%;
-}
-
-body {
-    height: 100%;
-    margin: 0;
-    padding: 0;
-    box-sizing: border-box;
-}
-
-main, header, footer {
-    position: static;
-    box-sizing: border-box;
-    width: 100%;
-    border: 0;
-    margin: 0;
-    overflow-x: hidden;
-}
-main .content, header .content, footer .content {
-    position: static;
-    box-sizing: border-box;
-    width: 100%;
-    border: 0;
-    margin: 0;
-}
-
-main .content {
-    position: absolute;
-    top: 0px; bottom: 0; left: 0; right: 0;
-    padding: 0; margin: 0;  /* override centering for static position */
-}
 #canvas {
     position: absolute;
     top: 0; bottom: 0; left: 0; right: 0; height: 100%; width: 100%;
     border: 0; margin: 0; padding: 0; outline: none;
     box-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
     border-radius: 2px;
+    background: $sec2_clr;
 }
-@media screen and (min-width: 1024px) { main .content {
-        left: calc(50% - 512px); right: calc(50% - 512px); width: 1024px;
-}}
-
-
-/**************** Basic things ****************/
-
-body {
-    font-family: "Ubuntu", Verdana, sans-serif;
-    background: #fff;
-    color: #222;
-    font-weight: normal;
-}
-
-p {
-    line-height: 140%;
-}
-
-a:link, a:visited, a:active {
-    color: #0B99DD;
-    text-decoration: none;
-}
-p a:link, p a:visited, p a:active, li a:link, li a:visited, li a:active {
-    text-decoration: none;
-}
-a:hover, p a:hover {
-    color: #0B99DD;
-    text-decoration: underline;
-}
-
-h1, h2, h3, h4 {
-    color: #07A82C;
-}
-h1 {
-    color: #000;
-}
-h1 { font-size: 220%; }
-h2 { font-size: 170%; }
-h3 { font-size: 120%; }
-h2 > a:link, h2 > a:visited, h2 > a:active, h3 > a:link, h3 > a:visited, h3 > a:active {
-    color: inherit;
-}
-h2 > a:hover, h3 > a:hover {
-    text-decoration: none;
-}
-
-hr {
-    height: 1px;
-    background: #ccc;
-    border: none;
-}
-
-code, .monospace {
-    font-family: Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;
-}
-
-code {
-    font-size: 90%;
-    color: #444;
-    background: #fff;
-    padding: 1px 5px;
-    white-space: nowrap;
-    border: solid 1px #e1e4e5;
-}
-
-button, a.button {
-    display: inline-block;
-    background: #eee;
-    color: #000;
-    padding: 0.3em 0.4em;
-    border-radius: 4px;
-    border: 1px solid #bbb;
-    transition: background 0.2s;
-}
-button:hover, a.button:hover {
-    text-decoration: none;
-    background: #f5f5f5;
-}
-button:disabled, button:disabled:hover {
-    background: #ddd;
-    color: #888;
-}
-
-main a.ctabutton {
-    display: inline-block;
-    border: none;
-    border-radius: 4px;
-    background: #0B99DD;
-    color: #fff;
-    padding: 1em;
-    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.4);
-    transition: box-shadow 0.1s;
-    font-size: 120%;
-    margin: 0.5em;
-}
-main a.ctabutton:hover {
-    text-decoration: none;
-    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.4);
-}
-
+body.darkmode #canvas { background: #171E28; }
 
 /**************** Popup Menu ****************/
 
 .dialog.verticalmenu {
-    background: #fff;
+    background: #f4f4f4;
     padding: 0;
     border: none;
 }
 .verticalmenu a, .dialog.verticalmenu .loggedinas {
     user-select: none;
     -moz-user-select: none;
-    color: #222;
+    color: $prim1_clr;
     display: block;
     box-sizing: border-box;
     border-bottom: 1px solid #bbb;
@@ -211,8 +82,9 @@ body.darkmode .dialog.verticalmenu {
     opacity: 0;
     padding: 0.4em;
     background: #fff;
-    border: 1px solid #eee;
-    border-radius: 2px;
+    border: none;
+    font-size: 85%;
+    border-radius: 3px;
     box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.25);
     transition: opacity 0.2s, left 0.2s, right 0.2s;
 }
@@ -256,8 +128,8 @@ body.darkmode .tooltipdiv {
 .dialog {
     box-sizing: border-box;
     outline: none;
-    background: #fff;
-    border: 6px solid #31383F;
+    background: $sec2_clr;
+    border: 6px solid $prim1_clr;
     position: absolute;
     left: 5%;
     right: 5%;
@@ -275,7 +147,8 @@ body.darkmode .tooltipdiv {
     transition: opacity 0.1s linear;
 }
 .dialog h2, .dialog h3 {
-    color: #000;  /* override red headers */
+    color: $prim1_clr;
+    font-family: $normalfont;
 }
 @media screen and (min-height: 600px) { .dialog {
     max-height: 90vh;
@@ -305,11 +178,11 @@ body.darkmode .tooltipdiv {
     padding-bottom: 0;
     line-height: 150%;
     font-size: 1.2em;
-    background: #31383F;
-    color: #fff;
+    background: $prim1_clr;
+    color: $sec1_clr;
 }
 .dialog h1 > i.fas {
-    color: #fff;
+    color: $sec1_clr;
 }
 .dialog h2 {
     font-size: 1.1em;
@@ -330,7 +203,7 @@ body.darkmode .tooltipdiv {
     border: 1px solid #bbb;
     border-radius: 4px;
     background: #fff;
-    color: #000;
+    color: $prim1_clr;
     padding: 0.3em 0.4em;
 }
 .dialog input:focus {
@@ -359,11 +232,11 @@ body.darkmode .tooltipdiv {
 
 .dialog h1 button {
     font-size: 100%;
-    color: #fff;
+    color: $sec1_clr;
     padding: 3px 0.5em 5px 0.5em;  /* Note: match with h1 padding-bottom */
     background: rgba(255, 255, 255, 0.1);
     border-radius: 2px;
-    border: 1px solid #eee;
+    border: 1px solid $sec2_clr;
     border-bottom: 0;
     border-left: 0;
     float: right;
@@ -382,20 +255,27 @@ body.darkmode .tooltipdiv {
 
 body.darkmode .dialog {
     background: #D3D8DA;
-    border-color: #31383F
+    border-color: $prim1_clr
 }
 body.darkmode .dialog h1 {
-    background: #31383F;
-    border-color: #31383F
+    background: $prim1_clr;
+    border-color: $prim1_clr
 }
 
 .dialog button.actionbutton {
     margin-left: 1em;
     font-size: 100%;
     padding: 0.5em 0.6em;
+    background: #fff;
+    border: none;
+    box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
+    transition: box-shadow 0.1s;
 }
-.dialog button.actionbutton.submit { background: #0B99DD; color: #fff; min-width: 8em; }
-.dialog button.actionbutton.submit:disabled { background: #0B99DD; color: #aaa; }
+.dialog button.actionbutton:hover { box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.4); }
+.dialog button.actionbutton.submit { background: $acc_clr; min-width: 8em; }
+.dialog button.actionbutton.submit:disabled { color: #aaa; }
+body.darkmode .dialog button.actionbutton { background: #eee; }
+body.darkmode .dialog button.actionbutton.submit { background: $acc_clr; }
 
 /* --- dialog table --- */
 
diff --git a/timetagger/app/demo.md b/timetagger/app/demo.md
index be5c8819..3923fac9 100644
--- a/timetagger/app/demo.md
+++ b/timetagger/app/demo.md
@@ -2,35 +2,69 @@
 % A live demo using simulated data.
 
 
-<script src='utils.js'></script>
-<script src='dt.js'></script>
-<script src='stores.js'></script>
-<script src='dialogs.js'></script>
-<script src='front.js'></script>
-<script src='jspdf.js'></script>
-<script src='Ubuntu-C-normal.js'></script>
-
 
 <script>
 
-// Uncomment to run demo at a specific moment in time, nice for making screenshots
-// var demodeltatime = dt.now() - new Date("2021-01-12T16:15:00").getTime() / 1000;
-// dt.now = function() { return new Date().getTime() / 1000 - demodeltatime};
-
 window.addEventListener("load", function() {
     if (!window.browser_supported) {return;}
     window.store = new window.stores.DemoDataStore();
     var canvas_element = document.getElementById('canvas');
     window.canvas = new window.front.TimeTaggerCanvas(canvas_element);
+
+    // Notify user that this is the Demo
     var dialog = new dialogs.DemoInfoDialog(window.canvas);
     setTimeout(dialog.open, 200);
+
+    // In the demo, enter dev-mode when serving on localhost
+    if (location.hostname == "localhost" || location.hostname == "127.0.0.1") {
+        enable_check_update_on_dbl_click();
+    }
 });
-</script>
 
+// Uncomment to run demo at a specific moment in time, nice for making screenshots
+// var demodeltatime = dt.now() - new Date("2021-01-12T16:15:00").getTime() / 1000;
+// dt.now = function() { return new Date().getTime() / 1000 - demodeltatime};
+
+
+function enable_check_update_on_dbl_click() {
+    // More of a dev-mode so we can make a change, restart server,
+    // and then double-click in app to auto-refresh when new version is detected.
+
+    // SW supported?
+    if (!('serviceWorker' in navigator)) { return; }
+
+    // Structure for the PWA workflow
+    window.pwa = {
+        sw_reg: null, // set when sw is registered
 
-<!-- Force preloading the font used in the canvas -->
-<span style='font-family: "Ubuntu Condensed"; color: #eee;'>demo</span>
-<span class='fas' style='color: #eee;'></span>
-<img id='ttlogo' alt='TimeTagger logo' src='timetagger192.png' width='2px' />
+        update: function () {
+            console.log("Checking for update ...")
+            if (window.pwa.sw_reg) { window.pwa.sw_reg.update(); }
+        },
+    };
+
+    // Register the service worker
+    navigator.serviceWorker.register('sw.js').then(reg => { window.pwa.sw_reg = reg; });
+
+    // Detect when a new service worker is activated. This happens after an update
+    // (or just after page load) when a new SW is found, installed, and activated.
+    var page_start_time = performance.now();
+    navigator.serviceWorker.addEventListener('controllerchange', function () {
+        console.log("New service worker detected.")
+        // Prevent continuous refresh when dev tool SW refresh is on
+        if (page_start_time === null) { return; }
+        if (performance.now() - page_start_time < 3000) {
+            page_start_time = null;
+            window.location.reload();  // User just arrived/refreshed, auto-refresh is ok
+        }
+    });
+
+    // Double-click invokes an update that will auto-refresh when a new version is found
+    document.body.ondblclick = function ()  {
+        page_start_time = performance.now();
+        window.pwa.update();
+    };
+}
+</script>
 
 <canvas id='canvas'>This page needs a working (HTML5) canvas.</canvas>
diff --git a/timetagger/app/dialogs.py b/timetagger/app/dialogs.py
index 1bb80c19..83fdfb40 100644
--- a/timetagger/app/dialogs.py
+++ b/timetagger/app/dialogs.py
@@ -37,7 +37,7 @@ def show_background_div(show, keep_transparent=False):
         )
 
     if show:
-        alpha = 0.0 if keep_transparent else 0.1
+        alpha = 0.0 if keep_transparent else 0.2
         window.dialogbackdiv.style.background = f"rgba(0, 0, 0, {alpha})"
         window.dialogbackdiv.style.pointerEvents = "auto"
         window.dialogbackdiv.style.display = "block"
@@ -308,7 +308,7 @@ def open(self):
         """Show/open the dialog ."""
         html = """
             <h1>Demo
-                <button type='button'>close <i class='fas'>\uf00d</i></button>
+                <button type='button'><i class='fas'>\uf00d</i></button>
             </h1>
             <p>
             This demo shows 5 years of randomly generated time tracking data.
@@ -333,7 +333,7 @@ def open(self):
         """Show/open the dialog ."""
         html = """
             <h1>Sandbox
-                <button type='button'>close <i class='fas'>\uf00d</i></button>
+                <button type='button'><i class='fas'>\uf00d</i></button>
             </h1>
             <p>
             The TimeTagger sandbox starts without any records. You can play around
@@ -359,7 +359,7 @@ def open(self, message):
         """Show/open the dialog ."""
         html = f"""
             <h1>Notification
-                <button type='button'>close <i class='fas'>\uf00d</i></button>
+                <button type='button'><i class='fas'>\uf00d</i></button>
             </h1>
             <p>{message}</p>
         """
@@ -385,7 +385,7 @@ def open(self):
         self.maindiv.innerHTML = f"""
             <div class='loggedinas'></div>
             <div class='divider'></div>
-            <a href="/"><img style='width:18px; height:18px;vertical-align:middle;' src='timetagger192.png' />&nbsp;&nbsp;Homepage</a>
+            <a href="/"><img style='width:20px; height:20px;vertical-align:middle;' src='timetagger_sd.svg' />&nbsp;&nbsp;Homepage</a>
             <a href="https://timetagger.app/support"><i class='fas'>\uf059</i>&nbsp;&nbsp;Get tips and help</a>
             <a href="../account"><i class='fas'>\uf2bd</i>&nbsp;&nbsp;Account</a>
             <div class='divider'></div>
@@ -439,7 +439,7 @@ def open(self):
             (
                 "\uf3fa",
                 is_installable,
-                "<span style='color:#0B99DD'>Install this app</span>",
+                "<span class='acc_color'>Install this app</span>",
                 self._do_install,
             ),
         ]:
@@ -1158,7 +1158,7 @@ def _query_tags(self):
         tags_html = "Tags: "
         if len(tags) == 0:
             tags = ["#untagged"]
-        tags_list = [f"<span style='color:#07A82C'>{t}</span>" for t in tags]
+        tags_list = [f"<span style='color:#000'>{t}</span>" for t in tags]
         tags_html += "&nbsp; &nbsp;".join(tags_list)
         # Get suggested tags
         self._suggested_tags_list = []
@@ -1899,15 +1899,15 @@ def _save_as_pdf(self):
         # Draw preamble
         doc.setFontSize(24)
         doc.text("Time record report", margin, margin, {"baseline": "top"})
-        img = document.getElementById("ttlogo")
-        doc.addImage(img, "PNG", width - margin - 20, margin, 20, 20)
-        doc.setFontSize(12)
-        doc.text(
-            "TimeTagger",
-            width - margin,
-            margin + 22,
-            {"align": "right", "baseline": "top"},
-        )
+        img = document.getElementById("ttlogo_bd")
+        doc.addImage(img, "PNG", width - margin - 30, margin, 30, 30)
+        # doc.setFontSize(12)
+        # doc.text(
+        #     "TimeTagger",
+        #     width - margin,
+        #     margin + 22,
+        #     {"align": "right", "baseline": "top"},
+        # )
 
         tagname = self._tags.join(" ") if self._tags else "all"
         d1 = reversed(dt.time2localstr(self._last_t1)[:10].split("-")).join("-")
diff --git a/timetagger/app/front.py b/timetagger/app/front.py
index 574ac115..f806d8bb 100644
--- a/timetagger/app/front.py
+++ b/timetagger/app/front.py
@@ -15,10 +15,11 @@
     BaseCanvas = object
 
 
-RECORD_AREA_ROUNDNESS = 5
-RECORD_ROUNDNESS = 5
-ANALYSIS_ROUNDNESS = 5
-BUTTON_RADIUS = 18
+SMALLER = 0.85
+BUTTON_ROUNDNESS = 4
+RECORD_AREA_ROUNDNESS = 4
+RECORD_ROUNDNESS = 6
+ANALYSIS_ROUNDNESS = 6
 
 PI = 3.141_592_653_589_793
 
@@ -29,31 +30,27 @@
     "size": 16,
     "condensed": "Ubuntu Condensed, Arial, sans-serif",
     "wide": "Ubuntu, Arial, sans-serif",
+    "mono": "Space Mono, Consolas, Monospace, Courier New",
     "default": "Ubuntu, Arial, sans-serif",
 }
 
 
-# Also see e.g. https://www.canva.com/colors/color-wheel/
-def set_colors():
+def init_module():
+    set_colors()
+    if window.matchMedia:
+        try:
+            window.matchMedia("(prefers-color-scheme: dark)").addEventListener(
+                "change", set_colors
+            )
+        except Exception:
+            pass  # e.g. Mobile Safari
 
-    # Unaffected by dark mode
-    COLORS.background3 = "rgb(22, 27, 34)"  # of the top
-    COLORS.header_text = "rgb(230, 230, 230)"
 
-    COLORS.button_text = "rgb(230, 230, 230)"
-    COLORS.button_text_disabled = "#555"
-    COLORS.button_title = utils.color_from_hue(240, 0.6, 0.99)  # 0B99DD
-    COLORS.button_bg1 = "rgb(16, 16, 16)"
-    COLORS.button_bg2 = "rgb(55, 55, 55)"
+window.addEventListener("load", init_module)
 
-    # Grays chosen to work in both light and dark mode
-    COLORS.tick_text = "rgba(130, 130, 130, 1)"
-    COLORS.tick_stripe1 = "rgba(130, 130, 130, 0.6)"  # day
-    COLORS.tick_stripe2 = "rgba(130, 130, 130, 0.3)"  # major
-    COLORS.tick_stripe3 = "rgba(130, 130, 130, 0.08)"  # minor
 
-    COLORS.record_between = "rgba(130, 130, 130, 0.1)"
-    COLORS.record_subtle_stripes = "rgba(130, 130, 130, 0.025)"
+# Also see e.g. https://www.canva.com/colors/color-wheel/
+def set_colors():
 
     # Dark vs light mode
     mode = 0
@@ -62,60 +59,65 @@ def set_colors():
         if ob is not None:
             mode = ob.value
     if mode == 1:
-        dark = False
+        light_mode = True
     elif mode == 2:
-        dark = True
+        light_mode = False
     else:
-        dark = False
+        light_mode = True
         if window.matchMedia:
             if window.matchMedia("(prefers-color-scheme: dark)").matches:
-                dark = True
+                light_mode = False
 
-    if dark:
+    # Theme palette
+    COLORS.prim1_clr = "#0F2C3E"
+    COLORS.prim2_clr = "#A4B0B8"
+    COLORS.sec1_clr = "#E6E7E5"
+    COLORS.sec2_clr = "#F4F4F4"
+    COLORS.acc_clr = "#DEAA22"
 
-        # App background (use rgba so the color can be re-used with different alpha)
-        COLORS.background1 = "rgba(13, 17, 23, 1)"
-        COLORS.background2 = "rgba(3, 7, 13, 1)"
+    # Grays chosen to work in both light and dark mode
+    COLORS.tick_text = "rgba(130, 130, 130, 1)"
+    COLORS.tick_stripe1 = "rgba(130, 130, 130, 0.6)"  # day
+    COLORS.tick_stripe2 = "rgba(130, 130, 130, 0.3)"  # major
+    COLORS.tick_stripe3 = "rgba(130, 130, 130, 0.08)"  # minor
 
-        COLORS.record_bg = "rgb(50, 55, 62)"
-        COLORS.record_text = "rgb(170, 170, 170)"
-        COLORS.record_text_tag = "rgb(240, 240, 240)"
-        COLORS.record_shadow = "rgba(0, 0, 0, 0.4)"
-        COLORS.record_edge = "rgb(75, 75, 75)"
-        COLORS.overview_bg = "rgb(30, 35, 42)"
+    if light_mode:
+        COLORS.background1 = "rgba(244, 244, 244, 1)"  # == #f4f4f4  - must end in "1)"
+        COLORS.top_bg = COLORS.prim1_clr
 
-        window.document.body.classList.add("darkmode")
-        window.document.body.style.background = "rgb(0, 0, 0)"
+        COLORS.panel_bg = COLORS.sec1_clr
+        COLORS.panel_edge = COLORS.prim1_clr
 
-    else:
+        COLORS.button_bg = "#fff"
+        COLORS.button_shadow = "rgba(0, 0, 0, 0.4)"
+        COLORS.button_text = COLORS.prim1_clr
+        COLORS.button_text_disabled = COLORS.prim2_clr
+
+        COLORS.record_bg = "#fafafa"
+        COLORS.record_text = COLORS.prim1_clr
+        COLORS.record_edge = COLORS.prim2_clr
 
+        window.document.body.classList.remove("darkmode")
+
+    else:
         # App background (use rgba so the color can be re-used with different alpha)
-        COLORS.background1 = "rgba(252, 252, 252, 1)"
-        COLORS.background2 = "rgba(255, 255, 255, 1)"
+        COLORS.background1 = "rgba(23, 30, 40, 1)"  # must end in "1)"
+        COLORS.top_bg = COLORS.prim1_clr
 
-        COLORS.record_bg = "rgb(240, 241, 245)"
-        COLORS.record_text = "rgb(75, 75, 75)"
-        COLORS.record_text_tag = "rgb(5, 5, 5)"
-        COLORS.record_shadow = "rgba(0, 0, 0, 0.4)"
-        COLORS.record_edge = "rgb(210, 217, 220)"
-        COLORS.overview_bg = "rgb(250, 250, 250)"
+        COLORS.panel_bg = COLORS.prim1_clr
+        COLORS.panel_edge = "#000"
 
-        window.document.body.classList.remove("darkmode")
-        bg3, bg4 = "rgb(255, 255, 255)", "rgb(245, 245, 245)"
-        window.document.body.style.background = (
-            f"linear-gradient(to bottom,{bg3},{bg4})"
-        )
+        COLORS.button_bg = "#bbb"  # COLORS.prim2_clr
+        COLORS.button_shadow = "rgba(0, 0, 0, 0.4)"
+        COLORS.button_text = COLORS.prim1_clr
+        COLORS.button_text_disabled = "#888"
 
+        COLORS.record_bg = "rgb(50, 55, 62)"
+        COLORS.record_text = "rgb(170, 170, 170)"
+        COLORS.record_edge = "rgb(75, 75, 75)"
 
-# Init colors
-set_colors()
-if window.matchMedia:
-    try:
-        window.matchMedia("(prefers-color-scheme: dark)").addEventListener(
-            "change", set_colors
-        )
-    except Exception:
-        pass  # e.g. Mobile Safari
+        window.document.body.classList.add("darkmode")
+        # window.document.body.style.background = "rgb(0, 0, 0)"
 
 
 class TimeTaggerCanvas(BaseCanvas):
@@ -178,14 +180,16 @@ def on_resize(self):
         """Perform layout; set sizes of widgets. We can go all responsive here."""
 
         margin = 5
-        margin2 = 5  # self.w / 40  --> handle margin inside analytics widget
-        space_to_divide = self.w - margin - margin2 - margin
+        space_to_divide = self.w - margin - margin
         if space_to_divide >= 785:
-            records_width = space_to_divide / 2
-        elif self._prefer_show_analytics:
-            records_width = 30
+            margin2 = 40
+            records_width = (space_to_divide - margin2) / 2
         else:
-            records_width = space_to_divide - 30
+            margin2 = 5
+            if self._prefer_show_analytics:
+                records_width = 30
+            else:
+                records_width = (space_to_divide - margin2) - 30
 
         # Determine splitter positions
         #
@@ -205,21 +209,16 @@ def on_resize(self):
         x4 = self.grid_round(x4)
 
         y0 = 0
-        y1 = self.grid_round(60)
-        y2 = self.grid_round(104)
+        y1 = self.grid_round(110)
+        y2 = self.grid_round(140)
         y3 = self.grid_round(self.h - 15)
 
         self.widgets["TopWidget"].rect = x0, y0, x5, y1
         self.widgets["RecordsWidget"].rect = x1, y2, x2, y3
         self.widgets["AnalyticsWidget"].rect = x3, y2, x4, y3
 
-        # Allocate space for fading in/out
-        self.future_pixels = self.grid_round(60)
-        self.past_pixels = self.grid_round(20)
-
-        # Determine reference font size
-        FONT.size = Math.round(min(max(space_to_divide / 30, 12), 16))
-        FONT.default = FONT.condensed if self.w < 600 else FONT.wide
+        # Determine reference font
+        FONT.default = FONT.condensed if self.w < 450 else FONT.wide
 
     def on_draw(self, ctx):
 
@@ -239,17 +238,29 @@ def on_draw(self, ctx):
         self.range.animation_update()
 
         # Clear / draw background
-        ctx.fillStyle = COLORS.background1
-        ctx.fillRect(0, 0, self.w, self.h)
+        ctx.clearRect(0, 0, self.w, self.h)
+        # ctx.fillStyle = COLORS.background1
+        # ctx.fillRect(0, 0, self.w, self.h)
+
+        # Draw icon in bottom right
+        iconw = 192 if self.w >= 400 else 96
+        iconh = iconw / 6
+        ctx.drawImage(
+            window.document.getElementById("ttlogo_tg"),
+            self.w - iconw - 5,
+            self.h - iconh - 5,
+            iconw,
+            iconh,
+        )
 
         if window.store.get_auth and not window.store.get_auth():
             # We need to be logged in, but we are not
             ctx.textAlign = "center"
             ctx.textBaseline = "middle"
-            ctx.fillStyle = COLORS.tick_text
-            text = "You need to login to use the TimeTagger app."
+            ctx.fillStyle = COLORS.prim1_clr
+            text = "You are loged out."
             utils.fit_font_size(ctx, self.w - 100, FONT.default, text, 30)
-            ctx.fillText(text, self.w / 2, self.h / 2)
+            ctx.fillText(text, self.w / 2, self.h / 3)
             # Draw menu and login button
             ctx.save()
             try:
@@ -562,17 +573,17 @@ def get_stat_period(self):
         t1, t2 = self.get_range()
         nsecs = t2 - t1
         if nsecs > 3 * 300 * 86400:
-            stat_period = "1Y"
+            stat_period = "1Y", "year"
         elif nsecs > 5 * 30 * 86400:
-            stat_period = "3M"  # a quarter
+            stat_period = "3M", "quarter"
         elif nsecs > 2.6 * 20 * 86400:
-            stat_period = "1M"
+            stat_period = "1M", "month"
         elif nsecs >= 10 * 86400:
-            stat_period = "1W"
+            stat_period = "1W", "week"
         elif nsecs > 4.1 * 1 * 86400:
-            stat_period = "1D"
+            stat_period = "1D", "day"
         else:
-            stat_period = None  # Don't draw stats, but records!
+            stat_period = None, ""  # Don't draw stats, but records!
         return stat_period
 
     def get_context_header(self):
@@ -595,7 +606,7 @@ def get_context_header(self):
 
         if day1 == day2:
             # Within a single day - finest granularity for the header
-            header = f"{weekday1}  {monthday1} {monthname1} {year1}"
+            header = f"{weekday1} {monthday1}  {monthname1} {year1}"
         elif day1[:7] == day2[:7]:
             # Within a single month
             if nsecs <= 86400 * 3:
@@ -604,12 +615,12 @@ def get_context_header(self):
             elif is_week_range and dt.is_first_day_of_week(t1):
                 # Exactly a calender week
                 wn = dt.get_weeknumber(t1)
-                header = f"Week {wn}  {monthday1}-{monthday2} {monthname1} {year1}"
+                header = f"Week {wn}  {monthday1}-{monthday2}  {monthname1} {year1}"
             elif nsecs <= 86400 * 14:
                 # Less than half a month
-                header = f"{monthday1} - {monthday2} {monthname1} {year1}"
+                header = f"{monthday1} - {monthday2}  {monthname1} {year1}"
             else:
-                header = f"{monthname1} {year1}"
+                header = f"{monthname1}  {year1}"
         elif day1[:4] == day2[:4]:
             # Within a single year
             if is_week_range and dt.is_first_day_of_week(t1):
@@ -618,22 +629,22 @@ def get_context_header(self):
                 header = f"Week {wn}  {monthname1} / {monthname2} {year1}"
             elif nsecs < 30 * 86400:
                 # Less than one month
-                header = f"{monthname1} / {monthname2} {year1}"
+                header = f"{monthname1} / {monthname2}  {year1}"
             else:
                 # Multi month
                 header = f"{year1}"
                 if day1[5:] == "01-01" and day2[5:] == "12-31":
                     pass
                 elif day1[5:] == "01-01" and day2[5:] == "03-31":
-                    header += " Q1"
+                    header = f"Q1  {year1}"
                 elif day1[5:] == "04-01" and day2[5:] == "06-30":
-                    header += " Q2"
+                    header = f"Q2  {year1}"
                 elif day1[5:] == "07-01" and day2[5:] == "09-30":
-                    header += " Q3"
+                    header = f"Q3  {year1}"
                 elif day1[5:] == "10-01" and day2[5:] == "12-31":
-                    header += " Q4"
+                    header = f"Q4  {year1}"
                 else:
-                    header = f"{monthname1} - {monthname2} {year1}"
+                    header = f"{monthname1} - {monthname2}  {year1}"
         else:
             # Multi-year
             if is_week_range and dt.is_first_day_of_week(t1):
@@ -676,6 +687,110 @@ def on_pointer_outside(self, ev):
     def on_draw(self, ctx):
         pass
 
+    def _draw_button(self, ctx, x, y, given_w, h, text, action, tt, options):
+        PSCRIPT_OVERLOAD = False  # noqa
+
+        # Set and collect options
+        opt = {
+            "font": FONT.default,
+            "ref": "topleft",
+            "color": COLORS.button_text,
+            "padding": 7,
+            "space": 5,
+            "body": True,
+        }
+        opt.update(options)
+
+        if text.toUpperCase:  # is string
+            texts = [text]
+        else:
+            texts = list(text)
+
+        # Measure texts
+        widths = []
+        fonts = []
+        for i in range(len(texts)):
+            text = texts[i]
+            if text.startswith("fas-"):
+                text = text[4:]
+                font = int(0.5 * h) + "px FontAwesome"
+            else:
+                font = int(0.5 * h) + "px " + opt.font
+            ctx.font = font
+            width = ctx.measureText(text).width
+            texts[i] = text
+            fonts.push(font)
+            widths.push(width)
+
+        # Determine width
+        needed_w = sum(widths) + 2 * opt.padding + opt.space * (len(widths) - 1)
+        if given_w:
+            w = given_w
+            # scale = min(1, given_w / needed_w)
+        else:
+            w = needed_w
+            # scale = 1
+
+        # Determine bounding box
+        if opt.ref.indexOf("right") >= 0:
+            x2 = x
+            x1 = x2 - w
+        elif opt.ref.indexOf("center") >= 0:
+            x1 = x - w / 2
+            x2 = x + w / 2
+        else:
+            x1 = x
+            x2 = x1 + w
+        #
+        if opt.ref.indexOf("bottom") >= 0:
+            y2 = y
+            y1 = y2 - h
+        elif opt.ref.indexOf("middle") >= 0:
+            y1 = y - h / 2
+            y2 = y + h / 2
+        else:
+            y1 = y
+            y2 = y1 + h
+
+        # Draw button body and its shadow
+        if opt.body:
+            ctx.fillStyle = COLORS.button_bg
+            rn = BUTTON_ROUNDNESS
+            for i in range(2):
+                dy = 2 if i == 0 else 0
+                ctx.beginPath()
+                ctx.arc(x1 + rn, y1 + dy + rn, rn, 1.0 * PI, 1.5 * PI)
+                ctx.arc(x2 - rn, y1 + dy + rn, rn, 1.5 * PI, 2.0 * PI)
+                ctx.arc(x2 - rn, y2 + dy - rn, rn, 0.0 * PI, 0.5 * PI)
+                ctx.arc(x1 + rn, y2 + dy - rn, rn, 0.5 * PI, 1.0 * PI)
+                ctx.closePath()
+                if i == 0:
+                    ctx.shadowBlur = 3
+                    ctx.shadowColor = COLORS.button_shadow
+                ctx.fill()
+                ctx.shadowBlur = 0
+
+        # Register the button and tooltip
+        ob = {"button": True, "action": action}
+        self._picker.register(x1, y1, x2, y2, ob)
+        if tt:
+            self._canvas.register_tooltip(x1, y1, x2, y2, tt, "below")
+
+        # Get starting x
+        x = x1 + opt.padding + 0.5 * (w - needed_w)
+
+        # Draw the text on top
+        ctx.textBaseline = "middle"
+        ctx.textAlign = "left"
+        ctx.fillStyle = opt.color
+        for i in range(len(texts)):
+            text, width, font = texts[i], widths[i], fonts[i]
+            ctx.font = font
+            ctx.fillText(text, x, 0.5 * (y1 + y2))
+            x += width + opt.space
+
+        return w
+
 
 class TopWidget(Widget):
     """Widget with menu, buttons, and time header."""
@@ -686,7 +801,6 @@ def on_init(self):
         self._current_scale = {}
         self._sync_feedback_xy = 0, 0
         window.setInterval(self._draw_sync_feedback_callback, 100)
-        self._top_offset = 20
 
         # For navigation with keys. Listen to canvas events, and window events (in
         # case canvas does not have focus), but don't listen for events from dialogs.
@@ -698,33 +812,148 @@ def on_draw(self, ctx, menu_only=False):
         self._picker.clear()
         x1, y1, x2, y2 = self.rect
 
-        ctx.fillStyle = COLORS.background3
-        ctx.fillRect(x1, y1, x2 - x1, y2 - y1)
+        y4 = y2  # noqa - bottom
+        y2 = y1 + 50
+        y3 = y2 + 12
+
+        h = 36
+
+        # Dark background wave (a cosine with the belly in the middle)
+        ctx.beginPath()
+        n = 20
+        amplitude = 3
+        period = 1.2 * 2 * PI
+        ctx.moveTo(x2 + 50, y2)
+        ctx.lineTo(x2 + 50, y1 - 50)
+        ctx.lineTo(x2, y1 - 50)
+        ctx.lineTo(x1, y1 - 50)
+        ctx.lineTo(x1 - 50, y1 - 50)
+        ctx.lineTo(x1 - 50, y2)
+        for i in range(n + 1):
+            x = x1 + i * (x2 - x1) / n
+            y = y2 - amplitude * Math.cos(period * (i / n - 0.5))
+            ctx.lineTo(x, y)
+        ctx.closePath()
+
+        ctx.fillStyle = COLORS.top_bg
+        ctx.fill()
 
-        header_text_width = (x2 - x1) / 3
         self._margin = margin = self._canvas.grid_round(max(2, (x2 - x1) / 30))
         x = x1 + 4
 
+        # Draw icon in top-right
+        iconsize = (x2 - x1) / 22
+        iconsize = 48
+        if iconsize:
+            ctx.drawImage(
+                window.document.getElementById("ttlogo_sl"),
+                x2 - iconsize,
+                y1 + 2,
+                iconsize,
+                iconsize,
+            )
+
         # Always draw the menu button
-        x += self._draw_menu_button(ctx, x, y1, x2, y2)
-        x += margin
+        self._draw_menu_button(ctx, x, y1, x2, y2)
 
+        # If menu-only, also draw login, then exit
         if menu_only:
-            # If menu-only, also draw login
-            x += self._draw_button(ctx, x, y1, "login", "login", "Login")
+            self._draw_button(
+                ctx,
+                0.5 * (x1 + x2),
+                y3,
+                None,
+                h,
+                "Login",
+                "login",
+                "",
+                {"ref": "topcenter"},
+            )
+            return
 
-        else:
-            # Otherwise ...
-            x += self._draw_tracking_buttons(ctx, x, y1, x2, y2)
-            x += margin
+        # Draw some more inside dark banner
+        self._draw_header_text(ctx, 60, y1, x2 - 60, y2 - 5)
+
+        # Draw buttons below the dark banner
+        # We go from the center to the sides
+        xc = 0.5 * (x1 + x2)
+
+        # Draw arrows
+        ha = 0.6 * h
+        yc = y3 + h / 2
+        dx = self._draw_button(
+            ctx,
+            xc,
+            yc - 1.5,
+            h,
+            ha,
+            "fas-\uf077",
+            "nav_backward",
+            "Step backward [↑/pageUp]",
+            {"ref": "bottomcenter"},
+        )
+        dx = self._draw_button(
+            ctx,
+            xc,
+            yc + 1.5,
+            h,
+            ha,
+            "fas-\uf078",
+            "nav_forward",
+            "Step forward [↓/pageDown]",
+            {"ref": "topcenter"},
+        )
 
-            x3 = x2 - header_text_width - 2 * BUTTON_RADIUS - margin
-            x += self._draw_nav_buttons(ctx, x, y1, x3, y2)
-            x += margin
+        # -- move to the left
+
+        x = xc - dx / 2 - 3
+
+        now_scale, now_clr = self._get_now_scale()
+        today_w = self._draw_button(
+            ctx,
+            x,
+            y3,
+            None,
+            h,
+            "Today",
+            "nav_snap_now" + now_scale,
+            "Snap to now [Home]",
+            {"ref": "topright", "color": now_clr, "font": FONT.condensed},
+        )
+
+        x -= today_w + margin
+
+        self._draw_tracking_buttons(ctx, x, y3, h)
+
+        # -- move to the right
+
+        x = xc + dx / 2 + 3
+
+        x += self._draw_button(
+            ctx,
+            x,
+            y3,
+            today_w,
+            h,
+            ["fas-\uf073", "fas-\uf0d7"],
+            "nav_menu",
+            "Select time range [t]",
+            {"ref": "topleft"},
+        )
 
-            x += self._draw_report_button(ctx, x, y1, x2, y2)
+        x += margin
 
-            self._draw_header_text(ctx, x, y1, x2, y2)
+        x += self._draw_button(
+            ctx,
+            x,
+            y3,
+            None,
+            h,
+            ["fas-\uf15c", "Report"],
+            "report",
+            "Show report [r]",
+            {"ref": "topleft", "font": FONT.condensed},
+        )
 
     def _draw_menu_button(self, ctx, x1, y1, x2, y2):
 
@@ -736,17 +965,25 @@ def _draw_menu_button(self, ctx, x1, y1, x2, y2):
         else:
             text = ""
 
-        # Draw buttons
-        x = x1
-        x += self._draw_sync_feedback(ctx, x, y1, x2, y2)
-        x += self._draw_button(ctx, x, y1, "fas-\uf0c9", "menu", "Show menu")
+        dx = self._draw_sync_feedback(ctx, 4, 4)
+
+        x = x1 + dx + 24
+
+        opt = {
+            "body": False,
+            "padding": 4,
+            "ref": "centermiddle",
+            "color": COLORS.sec2_clr,
+        }
+        self._draw_button(ctx, x, y1 + 18, None, 48, "fas-\uf0c9", "menu", "", opt)
 
         # Draw title
-        ctx.textAlign = "center"
-        ctx.textBaseline = "top"
-        ctx.font = "12px " + FONT.condensed
-        ctx.fillStyle = COLORS.button_title
-        ctx.fillText(text, (x1 + x) / 2, y1 + 5)
+        if text:
+            ctx.textAlign = "center"
+            ctx.textBaseline = "top"
+            ctx.font = "12px " + FONT.default
+            ctx.fillStyle = COLORS.acc_clr
+            ctx.fillText(text, x, 34)
 
         return x - x1
 
@@ -760,46 +997,38 @@ def _draw_sync_feedback_callback(self):
     def _draw_sync_feedback_work(self, register=True):
         PSCRIPT_OVERLOAD = False  # noqa
 
-        w, h = 10, BUTTON_RADIUS * 2
-
         if window.document.hidden:
-            return w
+            return
 
         ctx = self._canvas.node.getContext("2d")
-
-        x1, y1 = self._sync_feedback_xy
-        x, y = x1, y1 + self._top_offset
+        x, y = self._sync_feedback_xy
 
         # Get factor 0..1
         factor = window.store.sync_time
         factor = max(0, (factor[1] - dt.now()) / (factor[1] - factor[0] + 0.0001))
         factor = max(0, 1 - factor)
 
-        rn = 2
+        radius = 7
+        ctx.lineWidth = 2
 
-        # Draw container
+        # Clear bg
         ctx.beginPath()
-        ctx.arc(x + rn, y + rn, rn, 1.0 * PI, 1.5 * PI)
-        ctx.arc(x + w - rn, y + rn, rn, 1.5 * PI, 2.0 * PI)
-        ctx.arc(x + w - rn, y + h - rn, rn, 0.0 * PI, 0.5 * PI)
-        ctx.arc(x + rn, y + h - rn, rn, 0.5 * PI, 1.0 * PI)
-        ctx.closePath()
-        ctx.fillStyle = COLORS.background3
+        ctx.arc(x + radius, y + radius, radius + ctx.lineWidth, 0, 2 * PI)
+        ctx.fillStyle = COLORS.top_bg
         ctx.fill()
-        ctx.strokeStyle = COLORS.button_bg1
-        ctx.lineWidth = self._canvas.grid_round(1)
+
+        # Outline
+        ctx.beginPath()
+        ctx.arc(x + radius, y + radius, radius, 0, 2 * PI)
+        ctx.strokeStyle = "rgba(255, 255, 255, 0.3)"
         ctx.stroke()
 
-        # Draw progress
-        yo = (h - 2 * rn) * (factor)
+        # Progress
+        ref_angle = -0.5 * PI
         ctx.beginPath()
-        ctx.arc(x + rn, y + rn, rn, 1.0 * PI, 1.5 * PI)
-        ctx.arc(x + w - rn, y + rn, rn, 1.5 * PI, 2.0 * PI)
-        ctx.arc(x + w - rn, y + yo - rn, rn, 0.0 * PI, 0.5 * PI)
-        ctx.arc(x + rn, y + yo - rn, rn, 0.5 * PI, 1.0 * PI)
-        ctx.closePath()
-        ctx.fillStyle = COLORS.button_bg2
-        ctx.fill()
+        ctx.arc(x + radius, y + radius, radius, ref_angle, ref_angle + factor * 2 * PI)
+        ctx.strokeStyle = COLORS.prim2_clr
+        ctx.stroke()
 
         # Draw indicator icon - rotating when syncing
         M = dict(
@@ -814,26 +1043,27 @@ def _draw_sync_feedback_work(self, register=True):
         if text:
             ctx.save()
             try:
-                ctx.translate(x + w / 2, y + h / 2)
+                ctx.translate(x + radius, y + radius)
                 if state == "sync":
-                    ctx.rotate(((0.5 * time()) % 1) * 2 * Math.PI)
-                ctx.font = int(0.25 * h) + "px FontAwesome"
+                    ctx.rotate(((0.5 * time()) % 1) * 2 * PI)
+                ctx.font = (radius * 1.2) + "px FontAwesome"
                 ctx.textBaseline = "middle"
                 ctx.textAlign = "center"
-                ctx.fillStyle = COLORS.button_text
+                ctx.fillStyle = COLORS.prim2_clr
                 ctx.fillText(text, 0, 0)
             finally:
                 ctx.restore()
 
         # Register tiny sync button
         if register:
-            cm = 1
             ob = {"button": True, "action": "refresh", "help": ""}
-            self._picker.register(x - cm, y - cm, x + w + cm, y + h + cm, ob)
+            self._picker.register(
+                x - 1, y - 1, x + radius * 2 + 1, y + radius * 2 + 1, ob
+            )
 
-        return w
+        return 2 * radius
 
-    def _draw_tracking_buttons(self, ctx, x1, y1, x2, y2):
+    def _draw_tracking_buttons(self, ctx, x, y, h):
         PSCRIPT_OVERLOAD = False  # noqa
 
         now = self._canvas.now()
@@ -842,7 +1072,7 @@ def _draw_tracking_buttons(self, ctx, x1, y1, x2, y2):
         stop_tt = "Stop recording [x]"
 
         # Define stop summary
-        running_summary = "Record"
+        running_summary = ""
         records = window.store.records.get_running_records()
         has_running = False
         if len(records) > 0:
@@ -856,34 +1086,58 @@ def _draw_tracking_buttons(self, ctx, x1, y1, x2, y2):
                 else:
                     running_summary = "Timer running"
 
-        x = x1
-
-        # Start button
-        x += self._draw_button(ctx, x, y1, "fas-\uf04b", "record_start", start_tt)
-        x2 = x
+        x0 = x
 
         # Stop button
         if has_running:
-            x += self._draw_button(ctx, x, y1, "fas-\uf04d", "record_stopall", stop_tt)
-            x2 = x
+            dx = self._draw_button(
+                ctx,
+                x,
+                y,
+                h,
+                h,
+                "fas-\uf04d",
+                "record_stopall",
+                stop_tt,
+                {"ref": "topright", "font": FONT.condensed},
+            )
+            x -= dx + 3
+            dx = self._draw_button(
+                ctx,
+                x,
+                y,
+                h,
+                h,
+                "fas-\uf04b",
+                "record_start",
+                start_tt,
+                {"ref": "topright", "font": FONT.condensed},
+            )
+            x -= dx
         else:
-            x += x - x1
+            dx = self._draw_button(
+                ctx,
+                x,
+                y,
+                None,
+                h,
+                ["fas-\uf04b", "Record"],
+                "record_start",
+                start_tt,
+                {"ref": "topright", "font": FONT.condensed},
+            )
+            x -= dx
 
         # Draw summary text
         ctx.textBaseline = "top"
-        ctx.font = "12px " + FONT.condensed
-        ctx.fillStyle = COLORS.button_title
-        #
-        if True:
-            ctx.textAlign = "center"
-            ctx.fillText(running_summary, (x1 + x2) / 2, y1 + 5)
-        else:
-            ctx.textAlign = "center"
-            ctx.fillText("Record", (x1 + x) / 2, y1 + 5)
+        ctx.textAlign = "center"
+        ctx.font = "12px " + FONT.default
+        ctx.fillStyle = COLORS.prim2_clr
+        ctx.fillText(running_summary, (x0 + x) / 2, y + h + 5)
 
-        return x - x1
+        return x0 - x
 
-    def _draw_nav_buttons(self, ctx, x1, y1, x2, y2):
+    def _get_now_scale(self, ctx):
 
         t1, t2 = self._canvas.range.get_range()  # get_snap_range()
         nsecs = t2 - t1
@@ -902,7 +1156,7 @@ def _draw_nav_buttons(self, ctx, x1, y1, x2, y2):
             now_scale = "1Y"
 
         # Are we currently on one of the reference scales?
-        now_clr = None
+        now_clr = COLORS.button_text
         if len(now_scale):
             t1_now = dt.floor(now, now_scale)
             if t1 == t1_now and t2 == dt.add(t1_now, now_scale):
@@ -948,200 +1202,43 @@ def _draw_nav_buttons(self, ctx, x1, y1, x2, y2):
         self._current_scale["in"] = zoom_in_scale
         self._current_scale["out"] = zoom_out_scale
 
-        # Define buttons and display priority
-        # Alt home/day icons: uf783 uf015 uf185
-        buttons = [
-            (
-                8,
-                "Today",
-                "nav_snap_now" + now_scale,
-                "Snap to now [Home]",
-                now_clr,
-                BUTTON_RADIUS * 2.4,
-            ),
-            (7, "fas-\uf077", "nav_backward", "Step backward [↑/pageUp]"),
-            (7, "fas-\uf078", "nav_forward", "Step forward [↓/pageDown]"),
-            (
-                9,
-                "fas-\uf073 \uf0d7",
-                "nav_menu",
-                "Select time range [t]",
-                window.undefined,
-                BUTTON_RADIUS * 2.4,
-            ),
-        ]
-
-        skip = 2 * BUTTON_RADIUS
-        avail_size = x2 - x1
-
-        # Determine what buttons can be shown
-        ref_priority = 0
-        for ref_priority in range(0, 10):
-            sizes = [(skip if x[2] else x[1]) for x in buttons if x[0] >= ref_priority]
-            if sum(sizes) <= avail_size:
-                break
-        ref_priority = min(9, ref_priority)
-
-        # Draw buttons
-        x = x1
-        for i in range(len(buttons)):
-            priority, text, action, help, tcolor, w = buttons[i]
-            tcolor = tcolor or None
-            if priority >= ref_priority:
-                if action == "nav_backward":
-                    self._draw_button(
-                        ctx, x, y1, text, action, help, tcolor, w, BUTTON_RADIUS
-                    )
-                elif action == "nav_forward":
-                    x += self._draw_button(
-                        ctx,
-                        x,
-                        y1 + BUTTON_RADIUS,
-                        text,
-                        action,
-                        help,
-                        tcolor,
-                        w,
-                        BUTTON_RADIUS,
-                    )
-                else:
-                    x += self._draw_button(ctx, x, y1, text, action, help, tcolor, w)
-
-        # Draw summary text
-        ctx.textBaseline = "top"
-        ctx.font = "12px " + FONT.condensed
-        ctx.fillStyle = COLORS.button_title
-        ctx.textAlign = "center"
-        ctx.fillText("Navigate", (x1 + x) / 2, y1 + 5, x - x1)
-
-        return x - x1  # return the width
-
-    def _draw_report_button(self, ctx, x1, y1, x2, y2):
-
-        x = x1
-        x += self._draw_button(ctx, x1, y1, "fas-\uf15c", "report", "Show report [r]")
-
-        ctx.textAlign = "center"
-        ctx.textBaseline = "top"
-        ctx.font = "12px " + FONT.condensed
-        ctx.fillStyle = COLORS.button_title
-        ctx.fillText("Report", (x1 + x) / 2, y1 + 5)
-
-        return x - x1
+        return now_scale, now_clr
 
     def _draw_header_text(self, ctx, x1, y1, x2, y2):
 
         header = self._canvas.range.get_context_header() + " "  # margin
-        t1, t2 = self._canvas.range.get_range()
 
-        x3 = x1 + (x2 - x1) / 3
-        x4 = x2
-        tick_widget_in_use = self._canvas.widgets.RecordsWidget._pointer_pos  # noqa
+        x3 = (x1 + x2) / 2
+        y3 = y1 + 3
 
-        if True:  # not tick_widget_in_use:
-            # Draw header
-            ctx.fillStyle = COLORS.header_text
-            ctx.textBaseline = "top"
-            ctx.textAlign = "right"
-            #
-            size = utils.fit_font_size(ctx, x4 - x3, FONT.default, header, 40)
-            if size < 30 and "  " in header:
-                # Use two lines
-                text1, text2 = header.split("  ", 1)
-                size = utils.fit_font_size(ctx, x4 - x3, FONT.default, text2, 25)
-                ctx.fillText(text1 + " ", x4, y1 + 5)
-                ctx.fillText(text2, x4, y1 + 5 + 30)
-            else:
-                # Use one
-                ctx.fillText(header, x4, y1 + 5)
-        else:
-            # Draw more precise from-to
-            ctx.fillStyle = COLORS.header_text
-            ctx.textBaseline = "top"
-            ctx.textAlign = "left"
-            #
-            s1 = dt.time2localstr(t1) + "  to"
-            s2 = dt.time2localstr(t2)
-            if x4 - x3 > 300:  # PC
-                size = utils.fit_font_size(ctx, x4 - x3, FONT.default, s1, 25)
-                ctx.fillText(s1, x3, y1 + 5)
-                ctx.fillText(s2, x3, y1 + 5 + size + 2)
-            else:  # mobile
-                size = utils.fit_font_size(ctx, x4 - x3, FONT.default, s1, 20)
-                ctx.fillText(s1, x3, y1 + 15)
-                ctx.fillText(s2, x3, y1 + 15 + size + 2)
-
-    def _draw_button(
-        self,
-        ctx,
-        x1,
-        y1,
-        text,
-        action,
-        help,
-        tcolor=None,
-        w=BUTTON_RADIUS * 2,
-        h=BUTTON_RADIUS * 2,
-    ):
-        PSCRIPT_OVERLOAD = False  # noqa
-        # Register picking for this button
-        ob = {"button": True, "action": action, "help": help}
-        cm = 2  # click margin
-        x, y = x1, y1 + self._top_offset
-        self._picker.register(x - cm, y - cm, x + w + cm, y + h + cm, ob)
-        self._canvas.register_tooltip(x, y, x + w, y + h, help, "below")
-        # Draw it
-        return self._draw_button_noreg(ctx, x1, y1, text, action, help, tcolor, w, h)
-
-    def _draw_button_noreg(
-        self,
-        ctx,
-        x1,
-        y1,
-        text,
-        action,
-        help,
-        tcolor=None,
-        w=BUTTON_RADIUS * 2,
-        h=BUTTON_RADIUS * 2,
-    ):
-        PSCRIPT_OVERLOAD = False  # noqa
-        tcolor = tcolor or COLORS.button_text
-        bgcolor1 = COLORS.button_bg1
-        bgcolor2 = COLORS.button_bg2
-        x, y = x1, y1 + self._top_offset
-        # Prepare context
-        ctx.textBaseline = "middle"
+        # Draw header
+        ctx.textBaseline = "top"
         ctx.textAlign = "center"
-        grd = ctx.createLinearGradient(x, y, x, y + h)
-        grd.addColorStop(0.0, bgcolor2)
-        grd.addColorStop(1.0, bgcolor1)
-        ctx.fillStyle = grd
-        ctx.strokeStyle = bgcolor1
-        ctx.lineWidth = self._canvas.grid_round(1)
-        # Draw button bg
-        ctx.beginPath()
-        # ctx.rect(x, y, w, h)
-        rn = 2
-        ctx.arc(x + rn, y + rn, rn, 1.0 * PI, 1.5 * PI)
-        ctx.arc(x + w - rn, y + rn, rn, 1.5 * PI, 2.0 * PI)
-        ctx.arc(x + w - rn, y + h - rn, rn, 0.0 * PI, 0.5 * PI)
-        ctx.arc(x + rn, y + h - rn, rn, 0.5 * PI, 1.0 * PI)
-        ctx.closePath()
-        ctx.fill()
-        ctx.stroke()
-        # Draw content
-        ctx.fillStyle = tcolor
-        if text.startswith("fas-"):
-            ctx.font = "bold " + int(0.5 * h) + "px FontAwesome"
-            ctx.fillText(text[4:], x + w / 2, y + h / 2)
+        #
+        size = utils.fit_font_size(ctx, x2 - x1, FONT.default, header, 36)
+        text1, _, text2 = header.partition("  ")
+        if len(text2) == 0:
+            # One part
+            ctx.fillStyle = COLORS.sec2_clr
+            ctx.fillText(header, x3, y3)
+        elif size < 20:
+            # Two parts below each-other
+            size = utils.fit_font_size(ctx, x2 - x1, FONT.default, text2, 20)
+            ctx.fillStyle = COLORS.acc_clr
+            ctx.fillText(text1 + " ", x3, y3)
+            ctx.fillStyle = COLORS.sec2_clr
+            ctx.fillText(text2, x3, y1 + 5 + 18)
         else:
-            if len(text) > 1:
-                utils.fit_font_size(ctx, w * 0.80, FONT.condensed, text, 1.25 * h)
-            else:
-                ctx.font = int(h) + "px " + FONT.condensed
-            ctx.fillText(text, x + w / 2, y + h / 2)
-        return w
+            # Two parts next to each-other
+            text1 += "  "
+            w1 = ctx.measureText(text1).width
+            w2 = ctx.measureText(text2).width
+            w = w1 + w2
+            ctx.textAlign = "left"
+            ctx.fillStyle = COLORS.acc_clr
+            ctx.fillText(text1, x3 - w / 2, y3)
+            ctx.fillStyle = COLORS.sec2_clr
+            ctx.fillText(text2, x3 - w / 2 + w1, y3)
 
     def on_pointer(self, ev):
         x, y = ev.pos[0], ev.pos[1]
@@ -1316,7 +1413,7 @@ def on_draw(self, ctx):
             ctx.fill()
             ctx.textAlign = "center"
             ctx.textBaseline = "middle"
-            ctx.fillStyle = COLORS.tick_text
+            ctx.fillStyle = COLORS.prim1_clr
             ctx.font = FONT.size + "px " + FONT.default
             for i, c in enumerate("Records"):
                 ctx.fillText(c, (x3 + x4) / 2, (y3 + y4) / 2 + (i - 3) * 18)
@@ -1329,24 +1426,34 @@ def on_draw(self, ctx):
         x4 = self._canvas.grid_round(x3 + 50)
 
         # Draw background of "active region"
-        ctx.fillStyle = COLORS.background2
+        ctx.fillStyle = COLORS.panel_bg
         ctx.fillRect(x3, y1, x4 - x3, y2 - y1)
 
+        self._help_text = ""
+
         self._draw_ticks(ctx, x3, y1, x4, y2)
-        self._draw_shadow(ctx, x3, y1, x4, y2)
+        self._draw_edge(ctx, x3, y1, x4, y2)
         self._draw_record_area(ctx, x3, x4, x2, y1, y2)
-        self._draw_top_and_bottom_cover(ctx, x1, x3, x4, x2, 0, y1, 0.7)
+        ctx.clearRect(0, 0, self._canvas.w, y1 - 33)
+        self._draw_top_and_bottom_cover(ctx, x1, x3, x4, x2, y1 - 50, y1, 0.333)
         self._draw_top_and_bottom_cover(ctx, x1, x3, x4, x2, y2, self._canvas.h, -0.02)
 
-        # # Draw title text
-        # text1 = "Timeline"
-        # ctx.textAlign = "left"
-        # ctx.textBaseline = "bottom"
-        # ctx.fillStyle = COLORS.tick_stripe1
-        # ctx.font = (FONT.size * 1.4) + "px " + FONT.default
-        # ctx.fillText(text1, x4 + 25, y1 - 8)
+        # Draw title text
+        if self._canvas.w > 700:
+            text1 = "Timeline"
+            text2 = self._help_text
+            ctx.textAlign = "left"
+            ctx.textBaseline = "top"
+            #
+            ctx.font = "bold " + (FONT.size * 1.4) + "px " + FONT.mono
+            ctx.fillStyle = COLORS.prim2_clr
+            ctx.fillText(text1, 10, 65)
+            #
+            ctx.font = (FONT.size * 0.9) + "px " + FONT.default
+            ctx.fillStyle = COLORS.prim2_clr
+            ctx.fillText(text2, 10, 90)
 
-    def _draw_shadow(self, ctx, x1, y1, x2, y2):
+    def _draw_edge(self, ctx, x1, y1, x2, y2):
         def drawstrokerect(lw):
             rn = RECORD_AREA_ROUNDNESS + lw
             ctx.beginPath()
@@ -1357,38 +1464,39 @@ def drawstrokerect(lw):
             ctx.closePath()
             ctx.stroke()
 
-        lw = 1.2
+        lw = 3
         ctx.lineWidth = lw
         ctx.strokeStyle = COLORS.background1
         drawstrokerect(1.0 * lw)
-        ctx.strokeStyle = COLORS.tick_stripe2
+        ctx.strokeStyle = COLORS.panel_edge
         drawstrokerect(0.0 * lw)
 
     def _draw_top_and_bottom_cover(self, ctx, x1, x2, x3, x4, y1, y2, stop):
 
         grd1 = ctx.createLinearGradient(x1, y1, x1, y2)
         grd2 = ctx.createLinearGradient(x1, y1, x1, y2)
+        # grd3 = ctx.createLinearGradient(x1, y1, x1, y2)
         color1 = COLORS.background1
-        color2 = COLORS.background1.replace("1)", "0.0)")
-        color3 = COLORS.background1.replace("1)", "0.7)")
+        color2 = color1.replace("1)", "0.0)")
+        color4 = color1.replace("1)", "0.7)")
         if stop > 0:
-            grd1.addColorStop(0.0, color1)
+            grd1.addColorStop(0.0, color2)
             grd1.addColorStop(stop, color1)
             grd1.addColorStop(1.0, color2)
-            grd2.addColorStop(0.0, color1)
+            grd2.addColorStop(0.0, color2)
             grd2.addColorStop(stop, color1)
-            grd2.addColorStop(1.0, color3)
+            grd2.addColorStop(1.0, color4)
         else:
             grd1.addColorStop(0.0, color2)
             grd1.addColorStop(1 + stop, color1)
             grd1.addColorStop(1.0, color1)
-            grd2.addColorStop(0.0, color3)
+            grd2.addColorStop(0.0, color4)
             grd2.addColorStop(1 + stop, color1)
             grd2.addColorStop(1.0, color1)
         ctx.fillStyle = grd1
-        ctx.fillRect(x1, y1, x2 - x1, y2 - y1)
+        ctx.fillRect(0, y1, self._canvas.w, y2 - y1)
         ctx.fillStyle = grd2
-        ctx.fillRect(x2, y1, x4 - x2, y2 - y1)
+        ctx.fillRect(x2, y1, x3 - x2 + 50, y2 - y1 - 2)
 
     def _draw_ticks(self, ctx, x1, y1, x2, y2):
         PSCRIPT_OVERLOAD = False  # noqa
@@ -1403,7 +1511,7 @@ def _draw_ticks(self, ctx, x1, y1, x2, y2):
 
         # Prepare for drawing ticks
         ctx.fillStyle = COLORS.tick_text
-        ctx.font = FONT.size + "px " + FONT.condensed
+        ctx.font = (SMALLER * FONT.size) + "px " + FONT.default
         ctx.textBaseline = "middle"
         ctx.textAlign = "right"
 
@@ -1439,7 +1547,7 @@ def _draw_ticks(self, ctx, x1, y1, x2, y2):
                     text += " " + year
             elif granularity == "YY":
                 text = str(year)
-            ctx.fillText(text, x1 - 3, pos + y1, x1 - 3)
+            ctx.fillText(text, x1 - 4, pos + y1, x1 - 3)
 
         # Draw tick stripes
         ctx.strokeStyle = COLORS.tick_stripe2
@@ -1479,7 +1587,7 @@ def _draw_record_area(self, ctx, x1, x2, x3, y1, y2):
         npixels = y2 - y1
         nsecs = t2 - t1
         pps = npixels / nsecs
-        stat_period = self._canvas.range.get_stat_period()
+        stat_period, stat_name = self._canvas.range.get_stat_period()
 
         # Draw records or stats
         if stat_period is None:
@@ -1498,6 +1606,7 @@ def _draw_record_area(self, ctx, x1, x2, x3, y1, y2):
                 t3 = dt.add(t3, "1D")
             ctx.stroke()
         else:
+            self._help_text = "click on a " + stat_name + " to zoom"
             self._can_interact_with_records = False
             t3 = dt.floor(t1, stat_period)
             while t3 < t2:
@@ -1516,11 +1625,22 @@ def _draw_record_area(self, ctx, x1, x2, x3, y1, y2):
                 ctx.lineTo(x3, y3)
                 ctx.stroke()
                 t3 = t4
+            # Put border around
+            rn = RECORD_AREA_ROUNDNESS
+            ctx.beginPath()
+            ctx.arc(x3 - rn, y1 + rn, rn, 1.5 * PI, 2.0 * PI)
+            ctx.arc(x3 - rn, y2 - rn, rn, 0.0 * PI, 0.5 * PI)
+            ctx.arc(x2 + rn, y2 - rn, rn, 0.5 * PI, 1.0 * PI)
+            ctx.arc(x2 + rn, y1 + rn, rn, 1.0 * PI, 1.5 * PI)
+            ctx.closePath()
+            ctx.lineWidth = 3
+            ctx.strokeStyle = COLORS.prim1_clr
+            ctx.stroke()
 
         # Draw "now" - also if drawing stats
         t = self._canvas.now()
         y = y1 + (t - t1) * pps
-        ctx.strokeStyle = COLORS.button_title
+        ctx.strokeStyle = COLORS.prim1_clr
         ctx.lineWidth = 3  # Pretty thick so it sticks over other edges like week bounds
         ctx.setLineDash([4, 4])
         ctx.lineDashOffset = t % 8
@@ -1534,7 +1654,7 @@ def _draw_record_area(self, ctx, x1, x2, x3, y1, y2):
     def _draw_records(self, ctx, x1, x2, x3, y1, y2):
         PSCRIPT_OVERLOAD = False  # noqa
 
-        y0, y3 = 0, self._canvas.h
+        y0, y3 = y1 - 50, self._canvas.h
         t1, t2 = self._canvas.range.get_range()
         now = self._canvas.now()
 
@@ -1555,6 +1675,9 @@ def _draw_records(self, ctx, x1, x2, x3, y1, y2):
         # Select all records in this range. Sort so that smaller records are drawn on top.
         records = window.store.records.get_records(t1, t2).values()
 
+        if len(records) > 0:
+            self._help_text = "click a record to edit it"
+
         # Sort records by size, so records cannot be completely overlapped by another
         records.sort(key=lambda r: r.t1 - (now if (r.t1 == r.t2) else r.t2))
 
@@ -1720,10 +1843,6 @@ def _draw_one_record(
         #     if record.key == self._selected_record[0].key:
         #         selected_in_timeline = True
 
-        ctx.font = FONT.size + "px " + FONT.condensed  # or FONT.default?
-        ctx.textBaseline = "middle"
-        faded_clr = COLORS.tick_stripe1
-
         # Get position in pixels
         ry1 = y0 + npixels * (record.t1 - t1) / nsecs
         ry2 = y0 + npixels * (t2_or_now - t1) / nsecs
@@ -1741,18 +1860,6 @@ def _draw_one_record(
 
         timeline_only = ry2 < y1 or ry1 > y2
 
-        # # Draw shadow
-        # ctx.fillStyle = COLORS.record_bg
-        # ctx.shadowColor = COLORS.record_shadow
-        # ctx.beginPath()
-        # ctx.moveTo(x5, ty1 + rn)
-        # ctx.lineTo(x5, ty2)
-        # ctx.lineTo(x6 - rn, ty2)
-        # ctx.closePath()
-        # ctx.shadowBlur = rn
-        # ctx.fill()
-        # ctx.shadowBlur = 0
-
         # Draw record representation
         if timeline_only:
             ctx.beginPath()
@@ -1775,7 +1882,7 @@ def _draw_one_record(
         ctx.fillStyle = COLORS.record_bg
         ctx.fill()
         ctx.strokeStyle = COLORS.record_edge
-        ctx.lineWidth = 1.5
+        ctx.lineWidth = 1.2
         ctx.stroke()
 
         # Running records have a small outset
@@ -1791,7 +1898,7 @@ def _draw_one_record(
             ctx.fill()
             ctx.stroke()
             if int(now) % 2 == 1:
-                ctx.fillStyle = COLORS.tick_text
+                ctx.fillStyle = COLORS.prim1_clr
                 ctx.beginPath()
                 ctx.arc(0.5 * (x2 + x3), ry2 + outset / 2, 4, 0, 2 * PI)
                 ctx.fill()
@@ -1814,7 +1921,7 @@ def _draw_one_record(
         # The marker that indicates whether the record has been modified
         if record.st == 0:
             ctx.textAlign = "center"
-            ctx.fillStyle = COLORS.tick_text
+            ctx.fillStyle = COLORS.record_edge
             ctx.fillText("+", 0.5 * (x2 + x3), 0.5 * (ry1 + ry2))
 
         # Make the timeline-part clickable - the pick region is increased if needed
@@ -1834,6 +1941,10 @@ def _draw_one_record(
 
         text_ypos = ty1 + 0.55 * (ty2 - ty1)
 
+        ctx.font = (SMALLER * FONT.size) + "px " + FONT.default
+        ctx.textBaseline = "middle"
+        faded_clr = COLORS.prim2_clr
+
         # Draw duration text
         duration = record.t2 - record.t1
         if duration > 0:
@@ -1853,16 +1964,16 @@ def _draw_one_record(
             ctx.fillText(duration_sec, x5 + 30 + 1, text_ypos)
 
         # Show desciption
+        ctx.font = (SMALLER * FONT.size) + "px " + FONT.default
         ctx.textAlign = "left"
         max_x = x6 - 4
         space_width = ctx.measureText(" ").width + 2
         x = x5 + 55
         for part in ds_parts:
+            ctx.fillStyle = COLORS.record_text if tags_selected else faded_clr
             if part.startswith("#"):
-                ctx.fillStyle = COLORS.record_text_tag if tags_selected else faded_clr
                 texts = [part]
             else:
-                ctx.fillStyle = COLORS.record_text if tags_selected else faded_clr
                 texts = part.split(" ")
             for text in texts:
                 if len(text) == 0:
@@ -1899,11 +2010,6 @@ def _draw_selected_record_extras(
         x3 = x4
         x5 = x4 + 25  # noqa
 
-        # Prepare styles
-        body_style = COLORS.record_bg
-        border_style = COLORS.record_text
-        text_style = COLORS.tick_text
-
         # Get position in pixels
         ry1 = y0 + npixels * (record.t1 - t1) / nsecs
         ry2 = y0 + npixels * (record.t2 - t1) / nsecs
@@ -1914,11 +2020,9 @@ def _draw_selected_record_extras(
 
         # Prepare for drawing
         ctx.lineWidth = 1
-        ctx.fillStyle = body_style
-        ctx.strokeStyle = border_style
         ctx.textBaseline = "middle"
         ctx.textAlign = "center"
-        ctx.font = (FONT.size - 2) + "px " + FONT.condensed
+        ctx.font = (0.85 * FONT.size) + "px " + FONT.condensed
 
         # Prepare for drawing flaps
         inset = min(28, max(1, (ry2 - ry1) ** 0.5))
@@ -1948,9 +2052,9 @@ def _draw_selected_record_extras(
             ctx.arc(x1f + rn, ry1 - outset + rn, rn, 1.0 * PI, 1.5 * PI)
             ctx.arc(x2f - rn, ry1 - outset + rn, rn, 1.5 * PI, 2.0 * PI)
             ctx.lineTo(x2f, ry1 + inset)
-            ctx.fillStyle = body_style
+            ctx.fillStyle = COLORS.record_bg
             ctx.fill()
-            ctx.strokeStyle = border_style
+            ctx.strokeStyle = COLORS.record_edge
             ctx.stroke()
             # Shadow line
             ctx.beginPath()
@@ -1961,7 +2065,7 @@ def _draw_selected_record_extras(
             ctx.stroke()
             # Text
             timetext = dt.time2localstr(record.t1)[11:16]
-            ctx.fillStyle = text_style
+            ctx.fillStyle = COLORS.record_text
             ctx.fillText(timetext, 0.5 * (x1f + x2f), ry1 + (inset - outset) / 2)
 
         # Flat below to drag t2 - only present if not running
@@ -1975,9 +2079,9 @@ def _draw_selected_record_extras(
             ctx.arc(x2f - rn, ry2 + outset - rn, rn, 0.0 * PI, 0.5 * PI)
             ctx.arc(x1f + rn, ry2 + outset - rn, rn, 0.5 * PI, 1.0 * PI)
             ctx.lineTo(x1f, ry2 - inset)
-            ctx.fillStyle = body_style
+            ctx.fillStyle = COLORS.record_bg
             ctx.fill()
-            ctx.strokeStyle = border_style
+            ctx.strokeStyle = COLORS.record_edge
             ctx.stroke()
             # Shadow line
             ctx.beginPath()
@@ -1988,7 +2092,7 @@ def _draw_selected_record_extras(
             ctx.stroke()
             # Text
             timetext = dt.time2localstr(record.t2)[11:16]
-            ctx.fillStyle = text_style
+            ctx.fillStyle = COLORS.record_text
             ctx.fillText(timetext, 0.5 * (x1f + x2f), ry2 + (outset - inset) / 2)
 
         # Draw durarion on top
@@ -2064,53 +2168,21 @@ def _draw_stats(self, ctx, t1, t2, x1, y1, x2, y2, stat_period):
         bigfontsize = max(FONT.size, bigfontsize)
         ymargin = (y2 - y1) / 20
 
-        # if False:
-        #     # Score each tag
-        #     tag_scores1 = {}
-        #     for tagz, t in stats_dict.items():
-        #         tags = tagz.split(" ")
-        #         if len(selected_tags):
-        #             if not all([tag in tags for tag in selected_tags]):
-        #                 continue
-        #         for tag in tags:
-        #             tag_scores1[tag] = tag_scores1.get(tag, 0) + t
-        #     # Sort
-        #     tags_scored = []
-        #     for tag, score in tag_scores1.items():
-        #         tags_scored.push((tag, score))
-        #     tags_scored.sort(key=lambda x: x[1])
-        #     tags_scored = [x[0] for x in tags_scored]
-        #     # Draw tags
-        #     ctx.font = FONT.size + "px " + FONT.condensed
-        #     ctx.textBaseline = "top"
-        #     ctx.textAlign = "left"
-        #     ctx.fillStyle = COLORS.tick_stripe1
-        #     x, y = x2, y1 + (ymargin - 20)
-        #     for tag in tags_scored:
-        #         w = ctx.measureText(tag).width
-        #         if x + w > x2 - 5:
-        #             x = x1 + 10
-        #             y += 20
-        #             if y > y2 - 10:
-        #                 break
-        #         ctx.fillText(tag, x, y)
-        #         x += w + 10
-
         # Draw big text in blue if it is the timerange containing today
         if t1 < self._canvas.now() < t2:
-            ctx.fillStyle = COLORS.button_title
+            ctx.fillStyle = COLORS.prim1_clr
         else:
-            ctx.fillStyle = COLORS.tick_text
+            ctx.fillStyle = COLORS.prim2_clr
 
         # Draw duration at the left
-        ctx.font = f"{bigfontsize}px {FONT.condensed}"
+        ctx.font = f"{bigfontsize}px {FONT.default}"
         ctx.textBaseline = "bottom"
         ctx.textAlign = "left"
         duration = sumcount_selected if len(selected_tags) else sumcount
         ctx.fillText(f"{dt.duration_string(duration, False)}", x1 + 10, y2 - ymargin)
 
         # Draw time-range indication at the right
-        ctx.font = f"bold {bigfontsize}px {FONT.condensed}"
+        ctx.font = f"bold {bigfontsize}px {FONT.default}"
         ctx.textBaseline = "bottom"
         ctx.textAlign = "right"
         ctx.fillText(text, x2 - 10, y2 - ymargin)
@@ -2543,7 +2615,7 @@ def on_draw(self, ctx):
             ctx.fill()
             ctx.textAlign = "center"
             ctx.textBaseline = "middle"
-            ctx.fillStyle = COLORS.tick_text
+            ctx.fillStyle = COLORS.prim1_clr
             ctx.font = FONT.size + "px " + FONT.default
             for i, c in enumerate("Overview"):
                 ctx.fillText(c, (x3 + x4) / 2, (y3 + y4) / 2 + (i - 4) * 18)
@@ -2552,23 +2624,7 @@ def on_draw(self, ctx):
             )
             return
 
-        # X offset
-        x1 += max(5, (x2 - x1) / 20)
-
-        # Draw title text
-        text1 = "Overview"
-        text2 = "click a tag to select it"
-        ctx.textAlign = "left"
-        ctx.textBaseline = "bottom"
-        #
-        ctx.font = (FONT.size * 1.4) + "px " + FONT.default
-        ctx.fillStyle = COLORS.tick_stripe1  # COLORS.record_text
-        ctx.fillText(text1, x1, y1 - 8)
-        text1_width = ctx.measureText(text1).width
-        #
-        ctx.font = (FONT.size * 0.9) + "px " + FONT.default
-        ctx.fillStyle = COLORS.tick_stripe1
-        ctx.fillText(text2, x1 + text1_width + 10, y1 - 8)
+        self._help_text = ""
 
         # Process _time_at_last_draw, and set _time_since_last_draw
         time_now = time()
@@ -2580,7 +2636,6 @@ def on_draw(self, ctx):
 
         self._need_more_drawing_flag = False
 
-        # self.draw_image(ctx, x1, y1, x2, y2)
         self._draw_stats(ctx, x1, y1, x2, y2)
 
         if self._need_more_drawing_flag:
@@ -2589,6 +2644,21 @@ def on_draw(self, ctx):
         else:
             self._time_since_last_draw = 0
 
+        # Draw title text
+        if self._canvas.w > 700:
+            text1 = "Overview"
+            text2 = self._help_text
+            ctx.textAlign = "right"
+            ctx.textBaseline = "top"
+            #
+            ctx.font = "bold " + (FONT.size * 1.4) + "px " + FONT.mono
+            ctx.fillStyle = COLORS.prim2_clr
+            ctx.fillText(text1, x2 - 10, 65)
+            #
+            ctx.font = (FONT.size * 0.9) + "px " + FONT.default
+            ctx.fillStyle = COLORS.prim2_clr
+            ctx.fillText(text2, x2 - 10, 90)
+
         # Show some help if no records are shown
         if len(self._level_counts) == 1:
 
@@ -2598,26 +2668,9 @@ def on_draw(self, ctx):
             t1, t2 = self._canvas.range.get_range()
             if t1 < self._canvas.now() < t2:
                 ctx.textBaseline = "top"
-                ctx.fillStyle = COLORS.tick_text
-                text = "Hit the ▶ button to start tracking!"
-                ctx.fillText(text, x1 + 5, y1 + self._npixels_each + 15)
-
-    def draw_image(self, ctx, x1, y1, x2, y2):
-        y1 += 100
-        dx = x2 - x1 - 20
-        dy = y2 - y1 - 20
-        w = min(dx, dy)
-        if w < 0:
-            return
-        ctx.drawImage(
-            window.document.getElementById("ttlogo"),
-            (x1 + x2) / 2 - w / 2,
-            (y1 + y2) / 2 - w / 2,
-            w,
-            w,
-        )
-        ctx.fillStyle = COLORS.tick_stripe2
-        ctx.fillRect(x1, y1, x2 - x1, y2 - y1)
+                ctx.fillStyle = COLORS.prim1_clr
+                text = "Click the ▶ button to start tracking!"
+                ctx.fillText(text, x1 + 5, y1 + self._npixels_each + 25)
 
     def _draw_stats(self, ctx, x1, y1, x2, y2):
         PSCRIPT_OVERLOAD = False  # noqa
@@ -2723,7 +2776,7 @@ def _draw_stats(self, ctx, x1, y1, x2, y2):
         avail_inset = 80
         root_target_inset = -avail_inset * (Math.exp(-npixels / avail_inset) - 1)
         root_target_inset *= min(80, ((x2 - x1) / 6)) / 80  # responsive
-        npixels_each_min_max = 40, 100
+        npixels_each_min_max = 40, 60
 
         # Determine max level and derive more props
         avail_height = (y2 - y1) - (avail_inset / 4) * 2
@@ -2768,6 +2821,13 @@ def _draw_stats(self, ctx, x1, y1, x2, y2):
             if bar.height > 0:
                 self._draw_one_stat_unit(ctx, bar, root.cum_t)
 
+        # Determine help text
+        if self._maxlevel > 0:
+            if len(self.selected_tags) == 0:
+                self._help_text = "click a tag to filter"
+            else:
+                self._help_text = "click more tags to filter more"
+
     def _invalidate_element(self, d, parent=None):
         d.invalid = True
         # parent.subs.remove(d)
@@ -2951,9 +3011,8 @@ def _draw_one_stat_unit(self, ctx, unit, totaltime):
         PSCRIPT_OVERLOAD = False  # noqa
 
         t1, t2 = self._canvas.range.get_range()
-        x1, x2, x3, x4 = unit.x1, unit.x2, unit.x3, unit.x4
-        y1, y2, y3, y4 = unit.y1, unit.y2, unit.y3, unit.y4
-        y23, y14 = y3 - y2, y4 - y1
+        x2, x3 = unit.x2, unit.x3
+        y2, y3 = unit.y2, unit.y3
 
         # x_ref = self.rect[0] + self._max_cum_offset + 30
         x_ref = self.rect[0] + 42
@@ -2962,59 +3021,12 @@ def _draw_one_stat_unit(self, ctx, unit, totaltime):
         target_npixels = self._npixels_each
         npixels = min(y3 - y2, target_npixels)
 
-        # Define colors
-        body_style = COLORS.overview_bg
-        top_style = COLORS.tick_stripe2
-        left_style = COLORS.tick_stripe1
-        text_style = COLORS.record_text
-
-        font_size = FONT.size * 0.9 ** (unit.level - 1)
+        if is_root:
+            y3 += 8
 
         # Roundness
         rn = min(ANALYSIS_ROUNDNESS, npixels / 2)
 
-        # Stroke style for edges
-        ctx.strokeStyle = COLORS.tick_stripe3
-        ctx.lineWidth = 1.5
-
-        # Draw a shadow at the base
-        if is_root or unit.xoffset != 0:
-            ctx.fillStyle = left_style
-            ctx.beginPath()
-            if is_root:
-                ctx.shadowBlur = max(4, unit.target_inset / 4)
-                ctx.shadowColor = COLORS.record_shadow
-                ctx.arc(x3 - rn, y3 - rn, rn, 0.25 * PI, 0.5 * PI)  # bottom right
-                ctx.arc(x2 + rn, y3 - rn, rn, 0.5 * PI, 0.625 * PI)
-                ctx.arc(x1 + rn, y4 - rn, rn, 0.625 * PI, 1.0 * PI)  # bottom left
-                ctx.arc(x1 + rn, y1 + rn, rn, 1.0 * PI, 1.25 * PI)  # top left
-            else:
-                ctx.shadowBlur = abs(unit.xoffset)
-                ctx.shadowColor = COLORS.record_shadow
-                ctx.moveTo(x1, y1 + rn)
-                ctx.lineTo(x1, y4 - rn)
-                ctx.lineTo(x1 + min(x3 - x2, y3 - y2), (y1 + y4) / 2)
-            ctx.closePath()
-            ctx.fill()
-            ctx.shadowBlur = 0
-
-        # Gradient for lef/top part
-        grd = ctx.createLinearGradient(x1, y1 + rn, x1 + rn / 4, y1)
-        grd.addColorStop(0.0, left_style)
-        grd.addColorStop(1.0, top_style)
-
-        # Draw left/top
-        ctx.beginPath()
-        ctx.arc(x2 + rn, y3 - rn, rn, 0.5 * PI, 0.625 * PI)
-        ctx.arc(x1 + rn, y4 - rn, rn, 0.625 * PI, 1.0 * PI)  # bottom left
-        ctx.arc(x1 + rn, y1 + rn, rn, 1.0 * PI, 1.5 * PI)  # top left
-        ctx.arc(x4 - rn, y1 + rn, rn, 1.5 * PI, 1.625 * PI)
-        ctx.arc(x3 - rn, y2 + rn, rn, 1.625 * PI, 2.0 * PI)  # top right
-        # ctx.arc(x3 - rn, y3 - rn, rn, 0.0 * PI, 0.5 * PI)  # bottom right
-        ctx.closePath()
-        ctx.fillStyle = grd
-        ctx.fill()
-
         # Draw front
         ctx.beginPath()
         ctx.arc(x3 - rn, y2 + rn, rn, 1.5 * PI, 2.0 * PI)
@@ -3022,50 +3034,47 @@ def _draw_one_stat_unit(self, ctx, unit, totaltime):
         ctx.arc(x2 + rn, y3 - rn, rn, 0.5 * PI, 1.0 * PI)
         ctx.arc(x2 + rn, y2 + rn, rn, 1.0 * PI, 1.5 * PI)
         ctx.closePath()
-        ctx.fillStyle = body_style
+        if is_root:
+            ctx.lineWidth = 3
+            ctx.strokeStyle = COLORS.panel_edge
+            ctx.fillStyle = COLORS.panel_bg
+        else:
+            ctx.lineWidth = 1.2
+            ctx.strokeStyle = COLORS.record_edge
+            ctx.fillStyle = COLORS.record_bg
         ctx.fill()
         ctx.stroke()
 
-        # Subtle stripes
-        ctx.strokeStyle = COLORS.record_subtle_stripes
-        ctx.lineWidth = 1
-        ctx.beginPath()
-        for y in range(3, y23, 7):
-            ctx.moveTo(x3, y2 + y)
-            ctx.lineTo(x2, y2 + y)
-            ctx.lineTo(x1, y1 + y / y23 * y14)
-        ctx.stroke()
-
         # Draw more, or are we (dis)appearing?
         if unit.height / target_npixels < 0.3:
             return
 
         ymid = y2 + 0.55 * npixels
-        x_ref_color = x2 + 22  # center of dot
         x_ref_duration = x3 - 35  # right side of minute
-        x_ref_labels = x2 + 50  # start of labels
+        x_ref_labels = x2 + 30  # start of labels
 
-        # Draw coloured dot
+        # Draw coloured edge
         if unit.level > 0 and unit.level == self._maxlevel:
             ctx.beginPath()
-            ctx.arc(x_ref_color, ymid, 8, 0, 2 * PI)
+            ctx.arc(x2 + rn, y3 - rn, rn, 0.5 * PI, 1.0 * PI)
+            ctx.arc(x2 + rn, y2 + rn, rn, 1.0 * PI, 1.5 * PI)
             ctx.closePath()
             ctx.fillStyle = window.store.settings.get_color_for_tagz(unit.tagz)
             ctx.fill()
-            # That coloured edge is also a button
+            # That coloured region is also a button
             self._picker.register(
-                x_ref_color - 9,
-                ymid - 9,
-                x_ref_color + 9,
-                ymid + 9,
-                "chosecolor:" + unit.tagz,
+                x2,
+                y2,
+                x2 + rn,
+                y3,
+                {"button": True, "action": "chosecolor:" + unit.tagz},
             )
             tt_text = "Color for " + unit.tagz + "\n(Click to change color)"
             self._canvas.register_tooltip(
-                x_ref_color - 9,
-                ymid - 9,
-                x_ref_color + 9,
-                ymid + 9,
+                x2,
+                y2,
+                x2 + rn,
+                y3,
                 tt_text,
             )
 
@@ -3086,28 +3095,31 @@ def _draw_one_stat_unit(self, ctx, unit, totaltime):
 
         # Draw text labels
         tx, ty = x_ref_labels, ymid
-        dy = min(14, 0.8 * (y3 - y2))
-        ctx.font = font_size + "px " + FONT.default
-        ctx.fillStyle = text_style
+        ctx.font = FONT.size + "px " + FONT.default
+        ctx.fillStyle = COLORS.record_text
+        ctx.lineWidth = 1.2
+        ctx.strokeStyle = COLORS.acc_clr
         # Define text labels, and draw initial duration
         texts = []
         if is_root:
+            tx = x_ref
             if len(self.selected_tags):
-                tx = x_ref
                 texts.push([" ←  back to all ", "select:", "Full overview"])
             else:
                 ctx.textAlign = "right"
                 ctx.fillText(duration, x_ref_duration, ty)
                 if unit.cum_t > 0:
-                    texts.push(["total", ""])
+                    texts.push(["Total"])
                 else:
-                    texts.push(["(no records)", ""])
+                    texts.push(["Total"])
         else:
             ctx.textAlign = "right"
             ctx.fillText(duration, x_ref_duration, ty)
+            if len(self.selected_tags) and unit.level == 1:
+                texts.push(["Total of"])
             if duration_sec:
                 ctx.textAlign = "left"
-                ctx.fillStyle = COLORS.tick_stripe1
+                ctx.fillStyle = COLORS.prim2_clr
                 ctx.fillText(duration_sec, x_ref_duration + 1, ty)
             tags = [tag for tag in unit.subtagz.split(" ")]
             for tag in tags:
@@ -3118,45 +3130,33 @@ def _draw_one_stat_unit(self, ctx, unit, totaltime):
                     tt += " in total"
                     tt += "\n(Click to filter)"
                     texts.push([tag, "select:" + tag, tt])
-            if len(self.selected_tags) and unit.level == 1:
-                texts.push(["(subtotal)", ""])
         if unit.is_selected >= 2 and unit.cum_t > 0:
             texts.push([f" ({100*unit.percent_t:0.0f}%)", ""])
         # Draw text labels
         ctx.textAlign = "left"
-        ctx.lineWidth = 0.8
+        ctx.font = FONT.size + "px " + FONT.default
         for text, action, tt in texts:
-            dx = ctx.measureText(text).width
-            if text.startswith("#"):
-                ctx.fillStyle = COLORS.record_text
-            else:
-                ctx.fillStyle = text_style
-            ctx.fillText(text, tx, ty)
-            if action:
-                ctx.beginPath()
-                ctx.moveTo(tx - 3, ty - dy)
-                ctx.lineTo(tx + dx + 3, ty - dy)
-                ctx.lineTo(tx + dx + 3, ty + dy)
-                ctx.strokeStyle = COLORS.tick_stripe3
-                ctx.stroke()
+            if action and text.startswith("#"):
+                opt = {
+                    "ref": "leftmiddle",
+                    "body": False,
+                    "padding": 0,
+                    "color": COLORS.record_text,
+                }
+                dx = self._draw_button(ctx, tx, ty, None, 30, text, action, tt, opt)
                 ctx.beginPath()
-                ctx.moveTo(tx - 3, ty - dy)
-                ctx.lineTo(tx - 3, ty + dy)
-                ctx.lineTo(tx + dx + 3, ty + dy)
-                ctx.strokeStyle = COLORS.tick_stripe2
+                ctx.moveTo(tx, ty + 10)
+                ctx.lineTo(tx + dx, ty + 10)
                 ctx.stroke()
-                self._picker.register(tx - 4, ty - dy, tx + dx + 4, ty + dy, action)
-                if tt:
-                    self._canvas.register_tooltip(
-                        tx - 4,
-                        ty - dy,
-                        tx + dx + 4,
-                        ty + dy,
-                        tt,
-                        "below",
-                    )
-                dx += 2
-            tx += dx + 12
+                tx += dx + 12
+            elif action:
+                opt = {"ref": "leftmiddle"}
+                dx = self._draw_button(ctx, tx, ty, None, 30, text, action, tt, opt)
+                tx = dx + 4
+            else:
+                ctx.fillStyle = COLORS.record_text
+                ctx.fillText(text, tx, ty)
+                tx += ctx.measureText(text).width + 12
 
     def on_pointer(self, ev):
 
@@ -3166,7 +3166,7 @@ def on_pointer(self, ev):
             picked = self._picker.pick(x, y)
             if picked is None or picked == "":
                 pass
-            elif picked.button is True:
+            elif picked.button:
                 if picked.action == "showanalytics":
                     self._canvas._prefer_show_analytics = True
                     self._canvas.on_resize()
@@ -3174,18 +3174,18 @@ def on_pointer(self, ev):
                 elif picked.action == "report":
                     t1, t2 = self._canvas.range.get_range()
                     self._canvas.report_dialog.open(t1, t2, self.selected_tags)
-            elif picked.startswith("select:"):
-                _, _, tag = picked.partition(":")
-                if tag:
-                    if tag not in self.selected_tags:
-                        self.selected_tags.push(tag)
-                else:
-                    self.selected_tags = []
-            elif picked.startswith("chosecolor:"):
-                _, _, tagz = picked.partition(":")
-                if tagz:
-                    self._canvas.color_dialog.open(tagz, self.update)
-            self.update()
+                elif picked.action.startswith("select:"):
+                    _, _, tag = picked.action.partition(":")
+                    if tag:
+                        if tag not in self.selected_tags:
+                            self.selected_tags.push(tag)
+                    else:
+                        self.selected_tags = []
+                elif picked.action.startswith("chosecolor:"):
+                    _, _, tagz = picked.action.partition(":")
+                    if tagz:
+                        self._canvas.color_dialog.open(tagz, self.update)
+                self.update()
 
 
 if __name__ == "__main__":
diff --git a/timetagger/app/index.md b/timetagger/app/index.md
index 65047c78..13ca6ac4 100644
--- a/timetagger/app/index.md
+++ b/timetagger/app/index.md
@@ -2,16 +2,9 @@
 % The TimeTagger application.
 
 <script src='auth.js'></script>
-<script src='utils.js'></script>
-<script src='dt.js'></script>
-<script src='stores.js'></script>
-<script src='dialogs.js'></script>
-<script src='front.js'></script>
-<script src='jspdf.js'></script>
-<script src='Ubuntu-C-normal.js'></script>
-
 
 <script>
+
 window.addEventListener("load", function() {
     if (!window.browser_supported) {return;}
     window.store = new window.stores.ConnectedDataStore();
@@ -47,7 +40,7 @@ function register_service_worker() {
         show_refresh_button: function () {
             let style, html, el;
             style = 'background:#fff; color:#444; padding:0.3em; border: 1px solid #777; border-radius:4px; ';
-            style += 'position:absolute; top: 64px; left:4px; font-size:80%; '
+            style += 'position:absolute; top: 34px; left:4px; font-size:80%; '
             html = "<div style='" + style + "'>";
             html += "New version available, ";
             html += "<a href='#' onclick='location.reload();'>refresh</a>";
@@ -73,19 +66,16 @@ function register_service_worker() {
     var page_start_time = performance.now();
     navigator.serviceWorker.addEventListener('controllerchange', function () {
         console.log("New service worker detected.")
-        if (page_start_time === null) {
-            return;  // prevent continuous refresh when dev tool SW refresh is on
-        } else if (performance.now() - page_start_time < 3000) {
+        // Prevent continuous refresh when dev tool SW refresh is on
+        if (page_start_time === null) { return; }
+        if (performance.now() - page_start_time < 3000) {
+            page_start_time = null;
             window.location.reload();  // User just arrived/refreshed, auto-refresh is ok
         } else {
            window.pwa.show_refresh_button();  // Prompt the user to refresh instead
         }
-        page_start_time = null;
     });
 
-    // Show a message to promt the user to refresh the page
-
-
     // Auto-update each several hours
     var nhours = 4
     window.setInterval(() => {window.pwa.update()}, nhours * 60 * 60 * 1000);
@@ -93,9 +83,4 @@ function register_service_worker() {
 
 </script>
 
-<!-- Force preloading the font used in the canvas -->
-<img id='ttlogo' alt='TimeTagger logo' src='timetagger192.png' width='16px' />
-<span class='fas' style='color: #eee;'></span>
-<span style='font-family: "Ubuntu Condensed"; color: #eee;'>app</span>
-
 <canvas id='canvas'>This page needs a working (HTML5) canvas.</canvas>
diff --git a/timetagger/app/sandbox.md b/timetagger/app/sandbox.md
index 61bad0e2..87381b76 100644
--- a/timetagger/app/sandbox.md
+++ b/timetagger/app/sandbox.md
@@ -2,30 +2,19 @@
 % An empty app, not connected to the server, to try things out.
 
 
-<script src='utils.js'></script>
-<script src='dt.js'></script>
-<script src='stores.js'></script>
-<script src='dialogs.js'></script>
-<script src='front.js'></script>
-<script src='jspdf.js'></script>
-<script src='Ubuntu-C-normal.js'></script>
-
 
 <script>
+
 window.addEventListener("load", function() {
     if (!window.browser_supported) {return;}
     window.store = new window.stores.SandboxDataStore();
     var canvas_element = document.getElementById('canvas');
     window.canvas = new window.front.TimeTaggerCanvas(canvas_element);
+
+    // Notify user that this is the Sandbox
     var dialog = new dialogs.SandboxInfoDialog(window.canvas);
     setTimeout(dialog.open, 200);
 });
 </script>
 
-
-<!-- Force preloading the font used in the canvas -->
-<span style='font-family: "Ubuntu Condensed"; color: #eee;'>sandbox</span>
-<span class='fas' style='color: #eee;'></span>
-<img id='ttlogo' alt='TimeTagger logo' src='timetagger192.png' width='2px' />
-
 <canvas id='canvas'>This page needs a working (HTML5) canvas.</canvas>
diff --git a/timetagger/app/timetagger_manifest.json b/timetagger/app/timetagger_manifest.json
index 2a631e0a..8c9c09fb 100644
--- a/timetagger/app/timetagger_manifest.json
+++ b/timetagger/app/timetagger_manifest.json
@@ -7,19 +7,32 @@
     "start_url": "./",
     "scope": "./",
     "display": "fullscreen",
-    "theme_color": "#30b7ff",
-    "background_color": "#30b7ff",
+    "theme_color": "#F4F4F4",
+    "background_color": "#E6E7E5",
     "icons": [
         {
-            "src": "timetagger192filled.png",
+            "src": "timetagger192_sf.png",
+            "sizes": "192x192",
+            "type": "image/png",
+            "purpose": "any"
+        },
+        {
+            "src": "timetagger512_sf.png",
+            "sizes": "512x512",
+            "type": "image/png",
+            "purpose": "any"
+        },
+        {
+            "src": "timetagger192_sm.png",
             "sizes": "192x192",
             "type": "image/png",
             "purpose": "maskable"
         },
         {
-            "src": "timetagger512.png",
+            "src": "timetagger512_sm.png",
             "sizes": "512x512",
-            "type": "image/png"
+            "type": "image/png",
+            "purpose": "maskable"
         }
     ],
     "screenshots": [
diff --git a/timetagger/common/SpaceMono-Regular-webfont.woff b/timetagger/common/SpaceMono-Regular-webfont.woff
new file mode 100644
index 00000000..da768207
Binary files /dev/null and b/timetagger/common/SpaceMono-Regular-webfont.woff differ
diff --git a/timetagger/common/_style_embed.css b/timetagger/common/_style_embed.css
deleted file mode 100644
index 77c19445..00000000
--- a/timetagger/common/_style_embed.css
+++ /dev/null
@@ -1,76 +0,0 @@
-/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
-html{line-height:1.15;-webkit-text-size-adjust:100%}
-body{margin:0}
-h1{font-size:2em;margin:.67em 0}
-hr{box-sizing:content-box;height:0;overflow:visible}
-pre{font-family:monospace,monospace;font-size:1em}
-a{background-color:transparent}
-abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
-b,strong{font-weight:bolder}
-code,kbd,samp{font-family:monospace,monospace;font-size:1em}
-small{font-size:80%}
-sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
-sub{bottom:-.25em}
-sup{top:-.5em}
-img{border-style:none}
-button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}
-button,select{text-transform:none}
-button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}
-button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}
-button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}
-fieldset{padding:.35em .75em .625em}
-legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}
-progress{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-decoration{-webkit-appearance:none}
-::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
-details{display:block}
-summary{display:list-item}
-template{display:none}
-[hidden]{display:none}
-
-
-/**************** Fonts ****************/
-
-/* latin */
-@font-face {
-    font-family: 'Ubuntu';
-    font-style: normal;
-    font-weight: 400;
-    /*font-display: swap; does not work well with canvas? */
-    src: url(Ubuntu_latin.woff2) format('woff2');
-    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-/* latin */
-@font-face {
-    font-family: 'Ubuntu Condensed';
-    font-style: normal;
-    font-weight: 400;
-    /*font-display: swap;*/
-    src: url(UbuntuCondensed_latin.woff2) format('woff2');
-    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-/* FontAwesome 5 Free, only the solid version */
-@font-face {
-    font-family: 'FontAwesome';
-    font-style: normal;
-    font-weight: 900;
-    /*font-display: swap; 'block' cause falling back makes no sense - but works bad with canvas? */
-    src: url(fa-solid-900.woff2) format('woff2');
-}
-
-.fas {
-    font-family: 'FontAwesome';
-    font-weight: 900;
-    font-style: normal;
-    /* attempt to improve on OS X
-    font-smooth: auto;
-    -webkit-font-smoothing: antialiased;
-    text-rendering: optimizeLegibility;
-    */
-}
diff --git a/timetagger/common/_style_embed.scss b/timetagger/common/_style_embed.scss
new file mode 100644
index 00000000..baac0696
--- /dev/null
+++ b/timetagger/common/_style_embed.scss
@@ -0,0 +1,200 @@
+$prim1_clr: #0F2C3E;
+$prim2_clr: #A4B0B8;
+$sec1_clr: #E6E7E5;
+$sec2_clr: #F4F4F4;
+$acc_clr: #DEAA22;
+
+$bg1: #E6E7E5 url("paper1.jpg");
+$bg2: #F4F4F4 url("paper2.jpg");
+$bg3: #333333 url("paper3.jpg");
+
+$normalfont: "Ubuntu", Verdana, sans-serif;
+$narrowfont: "Ubuntu Condensed", Verdana, sans-serif;
+$monofont: "Space Mono", Consolas, "DejaVu Sans Mono", Monaco, "Courier New", Courier, monospace;
+
+
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],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}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{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-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
+
+/**************** Fonts ****************/
+
+/* latin */
+@font-face {
+    font-family: 'Ubuntu';
+    font-style: normal;
+    font-weight: 400;
+    /*font-display: swap; does not work well with canvas? */
+    src: url(Ubuntu_latin.woff2) format('woff2');
+    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* latin */
+@font-face {
+    font-family: 'Ubuntu Condensed';
+    font-style: normal;
+    font-weight: 400;
+    /*font-display: swap;*/
+    src: url(UbuntuCondensed_latin.woff2) format('woff2');
+    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+@font-face {
+    font-family: 'Space Mono';
+    src: url('SpaceMono-Regular-webfont.woff') format('woff');
+    font-weight: normal;
+    font-style: normal;
+}
+
+/* FontAwesome 5 Free, only the solid version */
+@font-face {
+    font-family: 'FontAwesome';
+    font-style: normal;
+    font-weight: 900;
+    /*font-display: swap; 'block' cause falling back makes no sense - but works bad with canvas? */
+    src: url(fa-solid-900.woff2) format('woff2');
+}
+
+.fas {
+    font-family: 'FontAwesome';
+    font-weight: 900;
+    font-style: normal;
+    /* attempt to improve on OS X
+    font-smooth: auto;
+    -webkit-font-smoothing: antialiased;
+    text-rendering: optimizeLegibility;
+    */
+}
+
+/**************** Main layout ****************/
+
+html {
+    height: 100%;
+}
+
+body {
+    height: 100%;
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+main, header, footer {
+    position: static;
+    box-sizing: border-box;
+    width: 100%;
+    border: 0;
+    margin: 0;
+    overflow-x: hidden;
+}
+main .content, header .content, footer .content {
+    position: static;
+    box-sizing: border-box;
+    width: 100%;
+    border: 0;
+    margin: 0;
+}
+
+main .content {
+    position: absolute;
+    top: 0px; bottom: 0; left: 0; right: 0;
+    padding: 1em; margin: 0;  /* override centering for static position */
+}
+@media screen and (min-width: 1024px) { main .content {
+    left: calc(50% - 512px); right: calc(50% - 512px); width: 1024px;
+}}
+
+/**************** Base styling ****************/
+
+body {
+    background: $bg2;
+    color: $prim1_clr;
+    font-family: $normalfont;
+    font-weight: normal;
+    font-size: 110%;
+}
+
+.acc_color { color: $acc_clr; }
+.normalfont { font-family: $normalfont; }
+.narrowfont { font-family: $narrowfont; }
+
+code, .monospace, .monofont { font-family: $monofont; }
+
+code {
+    font-size: 90%;
+    color: #444;
+    background: $sec2_clr;
+    padding: 1px 5px;
+    white-space: nowrap;
+    border: solid 1px #e1e4e5;
+}
+
+p {
+    line-height: 140%;
+}
+
+/**************** Links ****************/
+
+a:link, a:visited, a:active {
+    color: inherit;
+    text-decoration: none;
+    border-bottom: 1px solid $acc_clr;
+}
+p a:link, p a:visited, p a:active, li a:link, li a:visited, li a:active {
+    text-decoration: none;
+}
+a:hover, p a:hover {
+    color: inherit;
+    text-decoration: none;
+    border-bottom: 2px solid $acc_clr;
+}
+
+header a, footer a, a.link-icon {
+    border: none !important;  /* disable for specific cases */
+}
+
+/**************** Headers ****************/
+
+h1, h2, h3, h4 {
+    color: $prim2_clr;
+}
+h1 {
+    color: $prim1_clr;
+}
+h1 { font-size: 220%; }
+h2 { font-size: 150%; }
+h3 { font-size: 120%; }
+h2 > a:link, h2 > a:visited, h2 > a:active, h3 > a:link, h3 > a:visited, h3 > a:active {
+    color: inherit;
+}
+h2 > a:hover, h3 > a:hover {
+    text-decoration: none;
+}
+h1, h2 {
+    font-family: $monofont;
+}
+
+hr {
+    height: 1px;
+    background: #ccc;
+    border: none;
+}
+
+/**************** Buttons appear in the app, and e.g. /account ****************/
+
+button, a.button {
+    display: inline-block;
+    background: #eee;
+    color: $prim1_clr;
+    padding: 0.3em 0.4em;
+    border-radius: 4px;
+    border: 1px solid #bbb;
+    transition: background 0.2s;
+}
+button:hover, a.button:hover {
+    text-decoration: none;
+    background: #f5f5f5;
+}
+button:disabled, button:disabled:hover {
+    background: #ddd;
+    color: #888;
+}
diff --git a/timetagger/common/_template.html b/timetagger/common/_template.html
index 4850a26b..732d3798 100644
--- a/timetagger/common/_template.html
+++ b/timetagger/common/_template.html
@@ -5,16 +5,12 @@
   <title>{{ title }}</title>
   <meta name="description" content="{{ description }}">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <meta name="theme-color" content="#fff">
-  <link rel="icon" sizes="192x192" href="timetagger192filled.png">
-  <link rel="apple-touch-icon" href="timetagger192noalpha.png">
-
+  <meta name="theme-color" content="#F4F4F4">
+  <link rel="icon" sizes="192x192" href="timetagger192_sf.png">
+  <link rel="apple-touch-icon" href="timetagger192_sf.png">
   <style>
   {{ embedded_style }}
   </style>
-
-  <link rel="stylesheet" href="style.css">
-
 </head>
 
 <body>
diff --git a/timetagger/images/_names.txt b/timetagger/images/_names.txt
new file mode 100644
index 00000000..1acbd382
--- /dev/null
+++ b/timetagger/images/_names.txt
@@ -0,0 +1,13 @@
+* PNG's have their size in the name.
+* The suffix has 2 chars:
+  * type:
+    * "s" for small, square
+    * "w" for word, wide
+    * "b" for below, big
+    * "t" for text-only
+  * coloring:
+    * "d" for dark (to be used on a light bg)
+    * "l" for light (to be used on a dark bg)
+    * "g" for gray
+    * "f" for filled (dark on a light circle)
+    * "m" for maskable (dark on a white square)
diff --git a/timetagger/images/_update_icons.py b/timetagger/images/_update_icons.py
index 81cb5051..ac6ba711 100644
--- a/timetagger/images/_update_icons.py
+++ b/timetagger/images/_update_icons.py
@@ -6,14 +6,14 @@
 
 icon = webruntime.util.icon.Icon()
 for size in sizes:
-    fname = f"timetagger{size}.png"
+    fname = f"timetagger{size}_sf.png"
     icon.read(fname)
     # imb = imageio.imread(fname)
     # imw = 255 - imb  # make white the inverse
     # imw[:, :, 3] = imb[:, :, 3]  # Transfer original alpha channel
     # imageio.imsave(f"timetagger{size}w.png", imw)
 
-icon.write(f"timetagger.ico")
+icon.write(f"timetagger_sf.ico")
 icon.write(f"favicon.ico")
 
 # %%
diff --git a/timetagger/images/_update_paper.py b/timetagger/images/_update_paper.py
new file mode 100644
index 00000000..ff42e7e8
--- /dev/null
+++ b/timetagger/images/_update_paper.py
@@ -0,0 +1,94 @@
+"""
+Script to generate a pattern from an image.
+"""
+
+import numpy as np
+import imageio
+
+# Parameters
+ori = 10, 10
+size1 = 1200
+padding = 50
+size2 = size1 + 1 * padding
+clim = 220, 255
+
+# Take a square sample
+im = imageio.imread("paper0.jpg")
+rgba = np.zeros((size2, size2, 4)).astype(np.float32)
+rgba[:, :, :3] = im[ori[0] : ori[0] + size2, ori[1] : ori[1] + size2][:, :, :3]
+assert rgba.shape == (size2, size2, 4)
+
+# apply clim
+rgb = rgba[:, :, :3]
+rgb[rgb < clim[0]] = clim[0]
+rgb[rgb > clim[1]] = clim[1]
+
+
+def generate(fname, opacity, base_clr):
+
+    square = rgba.copy()
+
+    # Apply linear degrading opacity at the edges
+    for x in range(size2):
+        for y in range(size2):
+            fx = 1
+            if x < padding:
+                fx = x / padding
+            elif x > size2 - padding:
+                fx = (size2 - x) / padding
+            fy = 1
+            if y < padding:
+                fy = y / padding
+            elif y > size2 - padding:
+                fy = (size2 - y) / padding
+            square[y, x, 3] = opacity * fx * fy
+
+    # Cut out quadrants
+    #  q1 q2
+    #  q4 q3
+    h1 = size2 // 2
+    h2 = size2 // 2
+    q1 = square[:h2, :h2]
+    q2 = square[h1:, :h2]
+    q3 = square[h1:, h1:]
+    q4 = square[:h2, h1:]
+
+    # Prepare result
+    result = np.zeros((size1, size1, 4)).astype(np.float32)
+    result[:, :, :3] = base_clr * (1 - opacity)
+    result[:, :, 3] = 1 - opacity
+
+    # Blend quadrants in their oposite position
+    h3 = size1 // 2 - padding // 2
+    h4 = size1 // 2 + padding // 2
+    blend(result, q1, slice(h3, size1), slice(h3, size1))
+    blend(result, q2, slice(h3, size1), slice(0, h4))
+    blend(result, q3, slice(0, h4), slice(0, h4))
+    blend(result, q4, slice(0, h4), slice(h3, size1))
+
+    # Write result
+    assert result[:, :, 3].min() > 0.99 and result[:, :, 3].max() <= 1.001
+    result[result < 0] = 0
+    result[result > 255] = 255
+    imageio.imwrite(fname, result.astype(np.uint8)[:, :, :3])
+
+    # Show avg value
+    rgb = [result[:, :, i].mean() for i in range(3)]
+    print(f"mean rgb({rgb[0]:0.1f}, {rgb[1]:0.1f}, {rgb[2]:0.1f})")
+
+
+def blend(result, q, xslice, yslice):
+    for i in range(3):
+        result[yslice, xslice, i] += q[:, :, i] * q[:, :, 3]
+    result[yslice, xslice, 3] += q[:, :, 3]
+
+
+# -- paper 1 -> #E6E7E5 -> rgb(230, 231, 229)
+generate("paper1.jpg", 0.5, 220)
+
+# -- paper 2 -> #F4F4F4 -> rgb(244, 244, 244)
+# generate("paper4.jpg", 0.6, 255)
+generate("paper2.jpg", 0.5, 250)
+
+
+generate("paper3.jpg", 0.2, 0)
diff --git a/timetagger/images/favicon.ico b/timetagger/images/favicon.ico
index c52f36f6..6b1e6113 100644
Binary files a/timetagger/images/favicon.ico and b/timetagger/images/favicon.ico differ
diff --git a/timetagger/images/paper0.jpg b/timetagger/images/paper0.jpg
new file mode 100644
index 00000000..02fd665e
Binary files /dev/null and b/timetagger/images/paper0.jpg differ
diff --git a/timetagger/images/paper1.jpg b/timetagger/images/paper1.jpg
new file mode 100644
index 00000000..d0ebf5a7
Binary files /dev/null and b/timetagger/images/paper1.jpg differ
diff --git a/timetagger/images/paper2.jpg b/timetagger/images/paper2.jpg
new file mode 100644
index 00000000..f4411971
Binary files /dev/null and b/timetagger/images/paper2.jpg differ
diff --git a/timetagger/images/paper3.jpg b/timetagger/images/paper3.jpg
new file mode 100644
index 00000000..2203274b
Binary files /dev/null and b/timetagger/images/paper3.jpg differ
diff --git a/timetagger/images/timetagger.ico b/timetagger/images/timetagger.ico
deleted file mode 100644
index c52f36f6..00000000
Binary files a/timetagger/images/timetagger.ico and /dev/null differ
diff --git a/timetagger/images/timetagger128.png b/timetagger/images/timetagger128.png
deleted file mode 100644
index c7d9be59..00000000
Binary files a/timetagger/images/timetagger128.png and /dev/null differ
diff --git a/timetagger/images/timetagger128_sf.png b/timetagger/images/timetagger128_sf.png
new file mode 100644
index 00000000..130487d4
Binary files /dev/null and b/timetagger/images/timetagger128_sf.png differ
diff --git a/timetagger/images/timetagger16.png b/timetagger/images/timetagger16.png
deleted file mode 100644
index eba26045..00000000
Binary files a/timetagger/images/timetagger16.png and /dev/null differ
diff --git a/timetagger/images/timetagger16_sf.png b/timetagger/images/timetagger16_sf.png
new file mode 100644
index 00000000..37d796ad
Binary files /dev/null and b/timetagger/images/timetagger16_sf.png differ
diff --git a/timetagger/images/timetagger192.png b/timetagger/images/timetagger192.png
deleted file mode 100644
index ee480672..00000000
Binary files a/timetagger/images/timetagger192.png and /dev/null differ
diff --git a/timetagger/images/timetagger192_bd.png b/timetagger/images/timetagger192_bd.png
new file mode 100644
index 00000000..e72afce3
Binary files /dev/null and b/timetagger/images/timetagger192_bd.png differ
diff --git a/timetagger/images/timetagger192_sd.png b/timetagger/images/timetagger192_sd.png
new file mode 100644
index 00000000..f7d17b78
Binary files /dev/null and b/timetagger/images/timetagger192_sd.png differ
diff --git a/timetagger/images/timetagger192_sf.png b/timetagger/images/timetagger192_sf.png
new file mode 100644
index 00000000..74dda484
Binary files /dev/null and b/timetagger/images/timetagger192_sf.png differ
diff --git a/timetagger/images/timetagger192_sl.png b/timetagger/images/timetagger192_sl.png
new file mode 100644
index 00000000..8f193917
Binary files /dev/null and b/timetagger/images/timetagger192_sl.png differ
diff --git a/timetagger/images/timetagger192_sm.png b/timetagger/images/timetagger192_sm.png
new file mode 100644
index 00000000..0b5519f3
Binary files /dev/null and b/timetagger/images/timetagger192_sm.png differ
diff --git a/timetagger/images/timetagger192_tg.png b/timetagger/images/timetagger192_tg.png
new file mode 100644
index 00000000..1623a156
Binary files /dev/null and b/timetagger/images/timetagger192_tg.png differ
diff --git a/timetagger/images/timetagger192filled.png b/timetagger/images/timetagger192filled.png
deleted file mode 100644
index aab2da31..00000000
Binary files a/timetagger/images/timetagger192filled.png and /dev/null differ
diff --git a/timetagger/images/timetagger192noalpha.png b/timetagger/images/timetagger192noalpha.png
deleted file mode 100644
index 10c76d17..00000000
Binary files a/timetagger/images/timetagger192noalpha.png and /dev/null differ
diff --git a/timetagger/images/timetagger192soft.png b/timetagger/images/timetagger192soft.png
deleted file mode 100644
index fdf3603e..00000000
Binary files a/timetagger/images/timetagger192soft.png and /dev/null differ
diff --git a/timetagger/images/timetagger192w.png b/timetagger/images/timetagger192w.png
deleted file mode 100644
index 0ee10840..00000000
Binary files a/timetagger/images/timetagger192w.png and /dev/null differ
diff --git a/timetagger/images/timetagger192wsoft.png b/timetagger/images/timetagger192wsoft.png
deleted file mode 100644
index fb4cb19d..00000000
Binary files a/timetagger/images/timetagger192wsoft.png and /dev/null differ
diff --git a/timetagger/images/timetagger256.png b/timetagger/images/timetagger256.png
deleted file mode 100644
index 7794fe38..00000000
Binary files a/timetagger/images/timetagger256.png and /dev/null differ
diff --git a/timetagger/images/timetagger256_sf.png b/timetagger/images/timetagger256_sf.png
new file mode 100644
index 00000000..141a7adf
Binary files /dev/null and b/timetagger/images/timetagger256_sf.png differ
diff --git a/timetagger/images/timetagger32.png b/timetagger/images/timetagger32.png
deleted file mode 100644
index b7f71060..00000000
Binary files a/timetagger/images/timetagger32.png and /dev/null differ
diff --git a/timetagger/images/timetagger32_sf.png b/timetagger/images/timetagger32_sf.png
new file mode 100644
index 00000000..b2c1b3f3
Binary files /dev/null and b/timetagger/images/timetagger32_sf.png differ
diff --git a/timetagger/images/timetagger48.png b/timetagger/images/timetagger48.png
deleted file mode 100644
index 137ab8db..00000000
Binary files a/timetagger/images/timetagger48.png and /dev/null differ
diff --git a/timetagger/images/timetagger48_sf.png b/timetagger/images/timetagger48_sf.png
new file mode 100644
index 00000000..2cc2df8f
Binary files /dev/null and b/timetagger/images/timetagger48_sf.png differ
diff --git a/timetagger/images/timetagger512.png b/timetagger/images/timetagger512.png
deleted file mode 100644
index a0abf901..00000000
Binary files a/timetagger/images/timetagger512.png and /dev/null differ
diff --git a/timetagger/images/timetagger512_bd.png b/timetagger/images/timetagger512_bd.png
new file mode 100644
index 00000000..1667e5a6
Binary files /dev/null and b/timetagger/images/timetagger512_bd.png differ
diff --git a/timetagger/images/timetagger512_sf.png b/timetagger/images/timetagger512_sf.png
new file mode 100644
index 00000000..59140f96
Binary files /dev/null and b/timetagger/images/timetagger512_sf.png differ
diff --git a/timetagger/images/timetagger512_sm.png b/timetagger/images/timetagger512_sm.png
new file mode 100644
index 00000000..434f56a0
Binary files /dev/null and b/timetagger/images/timetagger512_sm.png differ
diff --git a/timetagger/images/timetagger64.png b/timetagger/images/timetagger64.png
deleted file mode 100644
index 24e96896..00000000
Binary files a/timetagger/images/timetagger64.png and /dev/null differ
diff --git a/timetagger/images/timetagger64_sf.png b/timetagger/images/timetagger64_sf.png
new file mode 100644
index 00000000..57160269
Binary files /dev/null and b/timetagger/images/timetagger64_sf.png differ
diff --git a/timetagger/images/timetagger_sd.svg b/timetagger/images/timetagger_sd.svg
new file mode 100644
index 00000000..62d3303a
--- /dev/null
+++ b/timetagger/images/timetagger_sd.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	viewBox="0 0 34.64 42.57" style="enable-background:new 0 0 34.64 42.57;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+	.T{fill:#DEAA22;}
+	.t{fill:#0F2C3E;}
+</style>
+<g>
+	<polygon class="T" points="18.51,25.88 1,25.88 1,31.7 6.85,31.7 6.85,41.57 12.67,41.57 12.67,31.7 18.51,31.7 	"/>
+	<polygon class="t" points="27.81,25.88 27.81,20.05 21.98,20.05 21.97,41.57 27.79,41.57 27.79,31.7 33.63,31.7 33.63,25.88 	"/>
+	<polygon class="T" points="16.13,16.69 33.64,16.69 33.64,10.87 27.8,10.87 27.8,1 21.98,1 21.98,10.87 16.13,10.87 	"/>
+	<polygon class="t" points="6.84,1 6.84,10.87 1,10.87 1,16.69 6.82,16.69 6.82,22.51 12.65,22.51 12.66,1 	"/>
+</g>
+</svg>
diff --git a/timetagger/images/timetagger_sf.ico b/timetagger/images/timetagger_sf.ico
new file mode 100644
index 00000000..6b1e6113
Binary files /dev/null and b/timetagger/images/timetagger_sf.ico differ
diff --git a/timetagger/images/timetagger_sl.svg b/timetagger/images/timetagger_sl.svg
new file mode 100644
index 00000000..2fa4b476
--- /dev/null
+++ b/timetagger/images/timetagger_sl.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	viewBox="0 0 34.64 42.57" style="enable-background:new 0 0 34.64 42.57;" xml:space="preserve">
+<style type="text/css">
+	.st0{	fill:#FFFFFF;}
+	.T{		fill:#DEAA22;}
+	.t{		fill:#F4F4F4;}
+</style>
+<g>
+	<polygon class="T" points="18.51,25.88 1,25.88 1,31.7 6.85,31.7 6.85,41.57 12.67,41.57 12.67,31.7 18.51,31.7 "/>
+	<polygon class="t" points="27.81,25.88 27.81,20.05 21.98,20.05 21.97,41.57 27.79,41.57 27.79,31.7 33.63,31.7 33.63,25.88 "/>
+	<polygon class="T" points="16.13,16.69 33.64,16.69 33.64,10.87 27.8,10.87 27.8,1 21.98,1 21.98,10.87 16.13,10.87 "/>
+	<polygon class="t" points="6.84,1 6.84,10.87 1,10.87 1,16.69 6.82,16.69 6.82,22.51 12.65,22.51 12.66,1 "/>
+</g>
+</svg>
diff --git a/timetagger/images/timetagger_wd.svg b/timetagger/images/timetagger_wd.svg
new file mode 100644
index 00000000..29d0ae2e
--- /dev/null
+++ b/timetagger/images/timetagger_wd.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	viewBox="0 0 295.2 42.57" style="enable-background:new 0 0 295.2 42.57;" xml:space="preserve">
+<style type="text/css">
+	.st0{		fill:#FFFFFF;}
+	.T{			fill:#DEAA22;}
+	.t{			fill:#0F2C3E;}
+	.time{		fill:#0F2C3E;}
+	.time_t{	fill:#0F2C3E;}
+	.tagger{	fill:#0F2C3E;}
+	.tagger_t{	fill:#0F2C3E;}
+</style>
+<g>
+	<g>
+		<polygon class="T" points="18.51,25.88 1,25.88 1,31.7 6.85,31.7 6.85,41.57 12.67,41.57 12.67,31.7 18.51,31.7 	"/>
+		<polygon class="t" points="27.81,25.88 27.81,20.05 21.98,20.05 21.97,41.57 27.79,41.57 27.79,31.7 33.63,31.7 33.63,25.88 	"/>
+		<polygon class="T" points="16.13,16.69 33.64,16.69 33.64,10.87 27.8,10.87 27.8,1 21.98,1 21.98,10.87 16.13,10.87 	"/>
+		<polygon class="t" points="6.84,1 6.84,10.87 1,10.87 1,16.69 6.82,16.69 6.82,22.51 12.65,22.51 12.66,1 	"/>
+	</g>
+	<g>
+		<!-- TIME -->
+		<polygon class="time_t" points="73.29,8.81 53.58,8.81 53.58,15.18 60.6,15.18 60.6,33.6 66.97,33.6 66.97,15.18 73.29,15.18"/>
+		<polygon class="time" points="89.31,15.18 93.67,15.18 93.67,8.84 78,8.84 78,15.18 82.93,15.18 82.92,15.78 82.9,27.23 78,27.25
+			78,33.59 93.67,33.59 93.67,27.25 89.25,27.24 			"/>
+		<path class="time" d="M123.11,16.63v16.96l6.42,0l-0.08-24.78h-6.95l-0.02,0.02c-3.03,0-5.92,1.17-8.13,3.28l-0.4,0.53l-0.47-0.53
+			c-2.19-2.09-5.04-3.26-8.05-3.28l-7.04-0.02l-0.01,24.79h6.34l0-17.82l0.01-0.52l0.62,0.02c2.94,0,5.32,2.38,5.34,5.31l0.02,0.02
+			v12.99h6.42V20.61l0.02-0.02c0.01-2.93,2.4-5.31,5.33-5.31l0.63,0v0.63V16.63z"/>
+		<polygon class="time" points="152.08,15.2 152.08,8.82 140.53,8.82 136.06,8.82 134.15,8.82 134.15,33.6 136.06,33.6 140.53,33.6
+			152.08,33.6 152.08,27.22 140.53,27.22 140.53,24.12 152.08,24.12 152.08,18.3 140.53,18.3 140.53,15.2"/>
+		<!-- tagger -->
+		<polygon class="tagger_t" points="165.36,17.08 165.36,33.6 161.97,33.6 161.97,11.2 161.97,8.81 165.36,8.81 165.36,13.68
+			165.36,13.68 170.64,13.68 170.64,17.08 			"/>
+		<path class="tagger" d="M196.09,30.92h-2.77v-1.6V18.66v-5.38h-3.17v3.18h-0.49c-0.65-1.28-1.54-2.22-2.67-2.83
+			c-1.13-0.61-2.38-0.92-3.77-0.92c-1.28,0-2.48,0.23-3.62,0.69c-1.14,0.46-2.14,1.13-2.99,2.02c-0.86,0.88-1.53,1.96-2.01,3.24
+			c-0.49,1.28-0.73,2.74-0.73,4.4v0.65c0,1.68,0.24,3.16,0.73,4.44c0.49,1.28,1.16,2.35,2.01,3.24c0.85,0.88,1.85,1.55,2.99,2
+			c1.14,0.45,2.37,0.67,3.7,0.67c1.28,0,2.49-0.33,3.64-0.98c1.15-0.65,2.05-1.66,2.71-3.01h0.49v0.24v3.38h3.17v0h2.77V30.92z
+			M189.66,26.66c-0.32,0.91-0.77,1.69-1.34,2.34c-0.57,0.65-1.26,1.15-2.05,1.51c-0.8,0.35-1.68,0.53-2.63,0.53
+			c-1.93,0-3.49-0.64-4.7-1.93c-1.21-1.29-1.81-3.09-1.81-5.39v-0.65c0-1.09,0.16-2.08,0.49-2.99c0.33-0.91,0.78-1.68,1.36-2.32
+			c0.58-0.64,1.27-1.13,2.06-1.49c0.79-0.35,1.66-0.53,2.6-0.53c0.92,0,1.78,0.18,2.58,0.55c0.8,0.37,1.49,0.87,2.08,1.53
+			c0.58,0.65,1.04,1.43,1.36,2.34c0.33,0.91,0.49,1.91,0.49,2.99v0.49C190.15,24.74,189.99,25.75,189.66,26.66z"/>
+		<path class="tagger" d="M215.76,30.39h-0.49c-0.65,1.22-1.54,2.14-2.67,2.75c-1.13,0.61-2.36,0.92-3.68,0.92
+			c-1.36,0-2.63-0.23-3.83-0.69c-1.19-0.46-2.25-1.13-3.16-1.99c-0.91-0.87-1.63-1.95-2.16-3.24c-0.53-1.29-0.79-2.76-0.79-4.42
+			v-0.65c0-1.63,0.27-3.09,0.81-4.38c0.54-1.29,1.26-2.37,2.16-3.26s1.94-1.55,3.13-2.02c1.19-0.46,2.44-0.69,3.75-0.69
+			c1.46,0,2.75,0.32,3.85,0.96c1.1,0.64,1.96,1.57,2.58,2.79h0.49v-3.18h3.17v25.16c0,0.92-0.29,1.68-0.87,2.28
+			c-0.58,0.6-1.34,0.9-2.26,0.9h-13.15v-2.93h12.01c0.73,0,1.1-0.41,1.1-1.22V30.39z M209,31.04c0.95,0,1.83-0.18,2.65-0.53
+			c0.81-0.35,1.53-0.85,2.14-1.51c0.61-0.65,1.09-1.43,1.45-2.34c0.35-0.91,0.53-1.92,0.53-3.03v-0.49c0-1.08-0.18-2.08-0.53-2.99
+			c-0.35-0.91-0.83-1.69-1.45-2.34c-0.61-0.65-1.33-1.16-2.16-1.53c-0.83-0.37-1.7-0.55-2.63-0.55c-0.95,0-1.84,0.18-2.67,0.53
+			c-0.83,0.35-1.55,0.85-2.16,1.51c-0.61,0.65-1.09,1.42-1.42,2.32c-0.34,0.9-0.51,1.89-0.51,2.97v0.65c0,1.14,0.17,2.16,0.51,3.05
+			c0.34,0.9,0.82,1.66,1.42,2.3c0.61,0.64,1.32,1.13,2.14,1.47C207.13,30.87,208.02,31.04,209,31.04z"/>
+		<path class="tagger" d="M240.67,30.39h-0.49c-0.65,1.22-1.54,2.14-2.67,2.75c-1.13,0.61-2.36,0.92-3.68,0.92
+			c-1.36,0-2.63-0.23-3.83-0.69c-1.19-0.46-2.25-1.13-3.16-1.99c-0.91-0.87-1.63-1.95-2.16-3.24c-0.53-1.29-0.79-2.76-0.79-4.42
+			v-0.65c0-1.63,0.27-3.09,0.81-4.38c0.54-1.29,1.26-2.37,2.16-3.26c0.9-0.88,1.94-1.55,3.13-2.02c1.19-0.46,2.44-0.69,3.75-0.69
+			c1.46,0,2.75,0.32,3.85,0.96c1.1,0.64,1.96,1.57,2.58,2.79h0.49v-3.18h3.17v25.16c0,0.92-0.29,1.68-0.87,2.28
+			c-0.58,0.6-1.34,0.9-2.26,0.9h-13.15v-2.93h12.01c0.73,0,1.1-0.41,1.1-1.22V30.39z M233.91,31.04c0.95,0,1.83-0.18,2.65-0.53
+			c0.81-0.35,1.53-0.85,2.14-1.51c0.61-0.65,1.09-1.43,1.45-2.34c0.35-0.91,0.53-1.92,0.53-3.03v-0.49c0-1.08-0.18-2.08-0.53-2.99
+			c-0.35-0.91-0.83-1.69-1.45-2.34c-0.61-0.65-1.33-1.16-2.16-1.53c-0.83-0.37-1.7-0.55-2.63-0.55c-0.95,0-1.84,0.18-2.67,0.53
+			c-0.83,0.35-1.55,0.85-2.16,1.51c-0.61,0.65-1.09,1.42-1.42,2.32c-0.34,0.9-0.51,1.89-0.51,2.97v0.65c0,1.14,0.17,2.16,0.51,3.05
+			c0.34,0.9,0.82,1.66,1.42,2.3c0.61,0.64,1.32,1.13,2.14,1.47C232.04,30.87,232.93,31.04,233.91,31.04z"/>
+		<path class="tagger" d="M252.56,24.36c0.08,2.01,0.71,3.62,1.87,4.84c1.17,1.22,2.79,1.83,4.88,1.83c0.95,0,1.78-0.11,2.48-0.33
+			c0.71-0.22,1.31-0.51,1.81-0.9c0.5-0.38,0.91-0.82,1.22-1.32c0.31-0.5,0.56-1.04,0.75-1.61l3.01,0.81
+			c-0.57,1.87-1.62,3.4-3.16,4.58c-1.53,1.18-3.6,1.77-6.21,1.77c-1.44,0-2.76-0.25-3.97-0.75c-1.21-0.5-2.25-1.21-3.11-2.14
+			c-0.87-0.92-1.55-2.02-2.04-3.3c-0.49-1.28-0.73-2.69-0.73-4.23v-0.98c0-1.41,0.25-2.73,0.75-3.95c0.5-1.22,1.2-2.27,2.1-3.16
+			c0.9-0.88,1.95-1.57,3.16-2.08c1.21-0.5,2.52-0.75,3.93-0.75c1.66,0,3.09,0.31,4.3,0.92c1.21,0.61,2.2,1.36,2.97,2.26
+			c0.77,0.9,1.34,1.87,1.71,2.93c0.37,1.06,0.55,2.03,0.55,2.93v2.61H252.56z M259.31,15.73c-0.9,0-1.73,0.14-2.5,0.43
+			c-0.77,0.28-1.45,0.69-2.04,1.2s-1.06,1.12-1.42,1.81c-0.37,0.69-0.6,1.45-0.71,2.26h13.03c-0.05-0.87-0.26-1.66-0.63-2.36
+			c-0.37-0.71-0.84-1.3-1.42-1.79c-0.58-0.49-1.25-0.87-1.99-1.14C260.87,15.87,260.1,15.73,259.31,15.73z"/>
+		<path class="tagger" d="M274.01,13.41h7.33v2.69h0.49c0.52-1.11,1.25-1.93,2.2-2.46s2.04-0.79,3.26-0.79c2.03,0,3.68,0.67,4.93,2
+			c1.25,1.33,1.91,3.27,1.99,5.82l-3.34,0.49c0-1.87-0.45-3.22-1.34-4.05c-0.9-0.83-2-1.24-3.3-1.24c-0.84,0-1.57,0.16-2.18,0.49
+			c-0.61,0.33-1.12,0.77-1.53,1.34c-0.41,0.57-0.71,1.24-0.9,2c-0.19,0.76-0.28,1.57-0.28,2.44v8.55h5.13v2.93h-12.95v-2.93h4.64
+			V16.34h-4.15V13.41z"/>
+	</g>
+</g>
+</svg>
diff --git a/timetagger/images/timetagger_wl.svg b/timetagger/images/timetagger_wl.svg
new file mode 100644
index 00000000..e3042926
--- /dev/null
+++ b/timetagger/images/timetagger_wl.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	viewBox="0 0 295.2 42.57" style="enable-background:new 0 0 295.2 42.57;" xml:space="preserve">
+<style type="text/css">
+	.st0{		fill:#000000;}
+	.T{			fill:#DEAA22;}
+	.t{			fill:#F4F4F4;}
+	.time{		fill:#F4F4F4;}
+	.time_t{	fill:#F4F4F4;}
+	.tagger{	fill:#F4F4F4;}
+	.tagger_t{	fill:#F4F4F4;}
+</style>
+<g>
+	<g>
+		<polygon class="T" points="18.51,25.88 1,25.88 1,31.7 6.85,31.7 6.85,41.57 12.67,41.57 12.67,31.7 18.51,31.7 	"/>
+		<polygon class="t" points="27.81,25.88 27.81,20.05 21.98,20.05 21.97,41.57 27.79,41.57 27.79,31.7 33.63,31.7 33.63,25.88 	"/>
+		<polygon class="T" points="16.13,16.69 33.64,16.69 33.64,10.87 27.8,10.87 27.8,1 21.98,1 21.98,10.87 16.13,10.87 	"/>
+		<polygon class="t" points="6.84,1 6.84,10.87 1,10.87 1,16.69 6.82,16.69 6.82,22.51 12.65,22.51 12.66,1 	"/>
+	</g>
+	<g>
+		<!-- TIME -->
+		<polygon class="time_t" points="73.29,8.81 53.58,8.81 53.58,15.18 60.6,15.18 60.6,33.6 66.97,33.6 66.97,15.18 73.29,15.18"/>
+		<polygon class="time" points="89.31,15.18 93.67,15.18 93.67,8.84 78,8.84 78,15.18 82.93,15.18 82.92,15.78 82.9,27.23 78,27.25
+			78,33.59 93.67,33.59 93.67,27.25 89.25,27.24 			"/>
+		<path class="time" d="M123.11,16.63v16.96l6.42,0l-0.08-24.78h-6.95l-0.02,0.02c-3.03,0-5.92,1.17-8.13,3.28l-0.4,0.53l-0.47-0.53
+			c-2.19-2.09-5.04-3.26-8.05-3.28l-7.04-0.02l-0.01,24.79h6.34l0-17.82l0.01-0.52l0.62,0.02c2.94,0,5.32,2.38,5.34,5.31l0.02,0.02
+			v12.99h6.42V20.61l0.02-0.02c0.01-2.93,2.4-5.31,5.33-5.31l0.63,0v0.63V16.63z"/>
+		<polygon class="time" points="152.08,15.2 152.08,8.82 140.53,8.82 136.06,8.82 134.15,8.82 134.15,33.6 136.06,33.6 140.53,33.6
+			152.08,33.6 152.08,27.22 140.53,27.22 140.53,24.12 152.08,24.12 152.08,18.3 140.53,18.3 140.53,15.2"/>
+		<!-- tagger -->
+		<polygon class="tagger_t" points="165.36,17.08 165.36,33.6 161.97,33.6 161.97,11.2 161.97,8.81 165.36,8.81 165.36,13.68
+			165.36,13.68 170.64,13.68 170.64,17.08 			"/>
+		<path class="tagger" d="M196.09,30.92h-2.77v-1.6V18.66v-5.38h-3.17v3.18h-0.49c-0.65-1.28-1.54-2.22-2.67-2.83
+			c-1.13-0.61-2.38-0.92-3.77-0.92c-1.28,0-2.48,0.23-3.62,0.69c-1.14,0.46-2.14,1.13-2.99,2.02c-0.86,0.88-1.53,1.96-2.01,3.24
+			c-0.49,1.28-0.73,2.74-0.73,4.4v0.65c0,1.68,0.24,3.16,0.73,4.44c0.49,1.28,1.16,2.35,2.01,3.24c0.85,0.88,1.85,1.55,2.99,2
+			c1.14,0.45,2.37,0.67,3.7,0.67c1.28,0,2.49-0.33,3.64-0.98c1.15-0.65,2.05-1.66,2.71-3.01h0.49v0.24v3.38h3.17v0h2.77V30.92z
+			M189.66,26.66c-0.32,0.91-0.77,1.69-1.34,2.34c-0.57,0.65-1.26,1.15-2.05,1.51c-0.8,0.35-1.68,0.53-2.63,0.53
+			c-1.93,0-3.49-0.64-4.7-1.93c-1.21-1.29-1.81-3.09-1.81-5.39v-0.65c0-1.09,0.16-2.08,0.49-2.99c0.33-0.91,0.78-1.68,1.36-2.32
+			c0.58-0.64,1.27-1.13,2.06-1.49c0.79-0.35,1.66-0.53,2.6-0.53c0.92,0,1.78,0.18,2.58,0.55c0.8,0.37,1.49,0.87,2.08,1.53
+			c0.58,0.65,1.04,1.43,1.36,2.34c0.33,0.91,0.49,1.91,0.49,2.99v0.49C190.15,24.74,189.99,25.75,189.66,26.66z"/>
+		<path class="tagger" d="M215.76,30.39h-0.49c-0.65,1.22-1.54,2.14-2.67,2.75c-1.13,0.61-2.36,0.92-3.68,0.92
+			c-1.36,0-2.63-0.23-3.83-0.69c-1.19-0.46-2.25-1.13-3.16-1.99c-0.91-0.87-1.63-1.95-2.16-3.24c-0.53-1.29-0.79-2.76-0.79-4.42
+			v-0.65c0-1.63,0.27-3.09,0.81-4.38c0.54-1.29,1.26-2.37,2.16-3.26s1.94-1.55,3.13-2.02c1.19-0.46,2.44-0.69,3.75-0.69
+			c1.46,0,2.75,0.32,3.85,0.96c1.1,0.64,1.96,1.57,2.58,2.79h0.49v-3.18h3.17v25.16c0,0.92-0.29,1.68-0.87,2.28
+			c-0.58,0.6-1.34,0.9-2.26,0.9h-13.15v-2.93h12.01c0.73,0,1.1-0.41,1.1-1.22V30.39z M209,31.04c0.95,0,1.83-0.18,2.65-0.53
+			c0.81-0.35,1.53-0.85,2.14-1.51c0.61-0.65,1.09-1.43,1.45-2.34c0.35-0.91,0.53-1.92,0.53-3.03v-0.49c0-1.08-0.18-2.08-0.53-2.99
+			c-0.35-0.91-0.83-1.69-1.45-2.34c-0.61-0.65-1.33-1.16-2.16-1.53c-0.83-0.37-1.7-0.55-2.63-0.55c-0.95,0-1.84,0.18-2.67,0.53
+			c-0.83,0.35-1.55,0.85-2.16,1.51c-0.61,0.65-1.09,1.42-1.42,2.32c-0.34,0.9-0.51,1.89-0.51,2.97v0.65c0,1.14,0.17,2.16,0.51,3.05
+			c0.34,0.9,0.82,1.66,1.42,2.3c0.61,0.64,1.32,1.13,2.14,1.47C207.13,30.87,208.02,31.04,209,31.04z"/>
+		<path class="tagger" d="M240.67,30.39h-0.49c-0.65,1.22-1.54,2.14-2.67,2.75c-1.13,0.61-2.36,0.92-3.68,0.92
+			c-1.36,0-2.63-0.23-3.83-0.69c-1.19-0.46-2.25-1.13-3.16-1.99c-0.91-0.87-1.63-1.95-2.16-3.24c-0.53-1.29-0.79-2.76-0.79-4.42
+			v-0.65c0-1.63,0.27-3.09,0.81-4.38c0.54-1.29,1.26-2.37,2.16-3.26c0.9-0.88,1.94-1.55,3.13-2.02c1.19-0.46,2.44-0.69,3.75-0.69
+			c1.46,0,2.75,0.32,3.85,0.96c1.1,0.64,1.96,1.57,2.58,2.79h0.49v-3.18h3.17v25.16c0,0.92-0.29,1.68-0.87,2.28
+			c-0.58,0.6-1.34,0.9-2.26,0.9h-13.15v-2.93h12.01c0.73,0,1.1-0.41,1.1-1.22V30.39z M233.91,31.04c0.95,0,1.83-0.18,2.65-0.53
+			c0.81-0.35,1.53-0.85,2.14-1.51c0.61-0.65,1.09-1.43,1.45-2.34c0.35-0.91,0.53-1.92,0.53-3.03v-0.49c0-1.08-0.18-2.08-0.53-2.99
+			c-0.35-0.91-0.83-1.69-1.45-2.34c-0.61-0.65-1.33-1.16-2.16-1.53c-0.83-0.37-1.7-0.55-2.63-0.55c-0.95,0-1.84,0.18-2.67,0.53
+			c-0.83,0.35-1.55,0.85-2.16,1.51c-0.61,0.65-1.09,1.42-1.42,2.32c-0.34,0.9-0.51,1.89-0.51,2.97v0.65c0,1.14,0.17,2.16,0.51,3.05
+			c0.34,0.9,0.82,1.66,1.42,2.3c0.61,0.64,1.32,1.13,2.14,1.47C232.04,30.87,232.93,31.04,233.91,31.04z"/>
+		<path class="tagger" d="M252.56,24.36c0.08,2.01,0.71,3.62,1.87,4.84c1.17,1.22,2.79,1.83,4.88,1.83c0.95,0,1.78-0.11,2.48-0.33
+			c0.71-0.22,1.31-0.51,1.81-0.9c0.5-0.38,0.91-0.82,1.22-1.32c0.31-0.5,0.56-1.04,0.75-1.61l3.01,0.81
+			c-0.57,1.87-1.62,3.4-3.16,4.58c-1.53,1.18-3.6,1.77-6.21,1.77c-1.44,0-2.76-0.25-3.97-0.75c-1.21-0.5-2.25-1.21-3.11-2.14
+			c-0.87-0.92-1.55-2.02-2.04-3.3c-0.49-1.28-0.73-2.69-0.73-4.23v-0.98c0-1.41,0.25-2.73,0.75-3.95c0.5-1.22,1.2-2.27,2.1-3.16
+			c0.9-0.88,1.95-1.57,3.16-2.08c1.21-0.5,2.52-0.75,3.93-0.75c1.66,0,3.09,0.31,4.3,0.92c1.21,0.61,2.2,1.36,2.97,2.26
+			c0.77,0.9,1.34,1.87,1.71,2.93c0.37,1.06,0.55,2.03,0.55,2.93v2.61H252.56z M259.31,15.73c-0.9,0-1.73,0.14-2.5,0.43
+			c-0.77,0.28-1.45,0.69-2.04,1.2s-1.06,1.12-1.42,1.81c-0.37,0.69-0.6,1.45-0.71,2.26h13.03c-0.05-0.87-0.26-1.66-0.63-2.36
+			c-0.37-0.71-0.84-1.3-1.42-1.79c-0.58-0.49-1.25-0.87-1.99-1.14C260.87,15.87,260.1,15.73,259.31,15.73z"/>
+		<path class="tagger" d="M274.01,13.41h7.33v2.69h0.49c0.52-1.11,1.25-1.93,2.2-2.46s2.04-0.79,3.26-0.79c2.03,0,3.68,0.67,4.93,2
+			c1.25,1.33,1.91,3.27,1.99,5.82l-3.34,0.49c0-1.87-0.45-3.22-1.34-4.05c-0.9-0.83-2-1.24-3.3-1.24c-0.84,0-1.57,0.16-2.18,0.49
+			c-0.61,0.33-1.12,0.77-1.53,1.34c-0.41,0.57-0.71,1.24-0.9,2c-0.19,0.76-0.28,1.57-0.28,2.44v8.55h5.13v2.93h-12.95v-2.93h4.64
+			V16.34h-4.15V13.41z"/>
+	</g>
+</g>
+</svg>
diff --git a/timetagger/pages/index.md b/timetagger/pages/index.md
index fb1a6b69..29c65f5d 100644
--- a/timetagger/pages/index.md
+++ b/timetagger/pages/index.md
@@ -1,12 +1,27 @@
 % TimeTagger - Tag your time, get the insight
 % An open source time tracker that feels lightweight and has powerful reporting.
 
+<img src='timetagger_wd.svg' width='350px' />
+<h1 class='normalfont'><span style='border-bottom: 3px solid #DEAA22;'>Tag</span> your time,<br>get the <span style='border-bottom: 3px solid  #DEAA22;'>insight</span>.</h1>
 
-<h1>
-    <img src='timetagger192.png' width='48px' />
-    TimeTagger
-</h1>
-<h2 style='color:#222;'><span style='color:#07A82C;'>Tag</span> your time,<br>get the <span style='color:#07A82C;'>insight</span>.</h2>
+<style>
+main a.ctabutton {
+    display: inline-block;
+    border: none;
+    border-radius: 4px;
+    background: #fff;
+    padding: 1em;
+    box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.4);
+    transition: box-shadow 0.1s;
+    font-size: 120%;
+    margin: 0.5em;
+}
+main a.ctabutton:hover {
+    text-decoration: none;
+    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.4);
+}
+
+</style>
 
 <div>
     <a href='app/demo' class='ctabutton'>
diff --git a/timetagger/server/_assets.py b/timetagger/server/_assets.py
index 88939672..82d3a701 100644
--- a/timetagger/server/_assets.py
+++ b/timetagger/server/_assets.py
@@ -12,6 +12,7 @@
 import jinja2
 import pscript
 import markdown
+import scss
 
 from .. import __version__
 
@@ -31,11 +32,28 @@
     .read()
     .decode()
 )
-style_embed = (
-    open(pkg_resources.resource_filename("timetagger.common", "_style_embed.css"), "rb")
-    .read()
-    .decode()
-)
+
+
+def _get_base_style():
+    fname = pkg_resources.resource_filename("timetagger.common", "_style_embed.scss")
+    with open(fname, "rb") as f:
+        text = f.read().decode()
+    style_embed = scss.compiler.compile_string(text)
+    # Find lines that define scss variables
+    var_lines = []
+    for line in text.splitlines():
+        line = line.strip()
+        if line.startswith("$") and ": " in line and line.endswith(";"):
+            var_lines.append(line)
+    preamble = "\n".join(var_lines) + "\n"
+    return style_embed, preamble
+
+
+style_embed, style_preamble = _get_base_style()
+
+
+def compile_scss(text):
+    return scss.compiler.compile_string(style_preamble + text)
 
 
 def md2html(text, template):
@@ -104,6 +122,10 @@ def create_assets_from_dir(dirname, template=None):
             html = md2html(text, template)
             name, ext = os.path.splitext(fname)
             assets["" if name == "index" else name] = html
+        elif fname.endswith((".scss", ".sass")):
+            # An scss/sass file, a preprocessor of css
+            text = open(os.path.join(dirname, fname), "rb").read().decode()
+            assets[fname[:-5] + ".css"] = compile_scss(text)
         elif fname.endswith(".html"):
             # Raw HTML
             text = open(os.path.join(dirname, fname), "rb").read().decode()


Test patch:
diff --git a/tests/test_server_assetserver.py b/tests/test_server_assetserver.py
index 08b70a98..a420e4ba 100644
--- a/tests/test_server_assetserver.py
+++ b/tests/test_server_assetserver.py
@@ -51,13 +51,13 @@ def test_assets():
         assert not r.body
 
         # Test known file asset
-        r = p.get("timetagger192.png")
+        r = p.get("timetagger192_sl.png")
         assert r.status == 200
         assert r.headers["content-type"] == "image/png"
         assert r.headers["etag"]
         assert r.headers["cache-control"]
         # Test caching with etag
-        r = p.get("timetagger192.png", headers={"if-none-match": r.headers["etag"]})
+        r = p.get("timetagger192_sl.png", headers={"if-none-match": r.headers["etag"]})
         assert r.status == 304
         assert not r.body