diff --git a/backend/main.go b/backend/main.go
index f7e8a0fe1bfa562fcbb3b5b160e77b2f7700b9a7..b7826b162204b5d43e410cf1e0d24b4506deb96f 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -7,12 +7,10 @@ import (
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-	"golang.org/x/sys/unix"
 	"gopkg.in/gographics/imagick.v3/imagick"
 	"log"
 	"net/http"
 	"os"
-	"os/signal"
 )
 
 var imageProcessDuration = promauto.NewCounterVec(prometheus.CounterOpts{
@@ -28,7 +26,7 @@ var imageProcessDurationWrite = imageProcessDuration.WithLabelValues("write")
 func main() {
 	configFile, err := os.Open("config.yaml")
 	if err != nil {
-		log.Fatalf("Could not open config file: %s", err.Error())
+		log.Fatalf("error opening config file: %s", err.Error())
 	}
 	config := shared.LoadConfigFromFile(configFile)
 
@@ -53,25 +51,17 @@ func main() {
 		Handler: metricsMux,
 	}
 
-	done := make(chan struct{})
 	go func() {
 		if err := metrics.ListenAndServe(); err != nil && err != http.ErrServerClosed {
-			log.Printf("Error: metrics server error: %s", err.Error())
+			log.Printf("error in metrics server: %s", err.Error())
 		}
-		close(done)
+		srv.Shutdown()
 	}()
 
 	if err := srv.Run(mux); err != nil {
-		log.Fatal(err)
+		log.Printf("error in asynq server: %s", err.Error())
 	}
-
-	sigs := make(chan os.Signal, 1)
-	signal.Notify(sigs, unix.SIGTERM, unix.SIGINT)
-	<-sigs
-
-	srv.Shutdown()
 	if err := metrics.Shutdown(context.Background()); err != nil {
-		log.Printf("Error: metrics shutdown error: %s", err.Error())
+		log.Printf("error shutting down metrics server: %s", err.Error())
 	}
-	<-done
 }
diff --git a/frontend/assets/css/fonts.css b/frontend/assets/css/fonts.css
deleted file mode 100644
index b2c0f8a8d213cda12525152661a7cc01b8eb1742..0000000000000000000000000000000000000000
--- a/frontend/assets/css/fonts.css
+++ /dev/null
@@ -1 +0,0 @@
-@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-Regular.eot");src:url("/assets/fonts/Lato-Regular.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-Regular.woff2") format("woff2"),url("/assets/fonts/Lato-Regular.woff") format("woff"),url("/assets/fonts/Lato-Regular.ttf") format("truetype");font-style:normal;font-weight:normal;text-rendering:optimizeLegibility}@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-Bold.eot");src:url("/assets/fonts/Lato-Bold.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-Bold.woff2") format("woff2"),url("/assets/fonts/Lato-Bold.woff") format("woff"),url("/assets/fonts/Lato-Bold.ttf") format("truetype");font-style:normal;font-weight:bold;text-rendering:optimizeLegibility}@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-BoldItalic.eot");src:url("/assets/fonts/Lato-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-BoldItalic.woff2") format("woff2"),url("/assets/fonts/Lato-BoldItalic.woff") format("woff"),url("/assets/fonts/Lato-BoldItalic.ttf") format("truetype");font-style:italic;font-weight:bold;text-rendering:optimizeLegibility}@font-face{font-family:'Lato';src:url("/assets/fonts/Lato-Italic.eot");src:url("/assets/fonts/Lato-Italic.eot?#iefix") format("embedded-opentype"),url("/assets/fonts/Lato-Italic.woff2") format("woff2"),url("/assets/fonts/Lato-Italic.woff") format("woff"),url("/assets/fonts/Lato-Italic.ttf") format("truetype");font-style:italic;font-weight:normal;text-rendering:optimizeLegibility}
diff --git a/frontend/assets/css/style.css b/frontend/assets/css/style.css
deleted file mode 100644
index c5636cdab3259e1b2d480c168d74264aadcaed44..0000000000000000000000000000000000000000
--- a/frontend/assets/css/style.css
+++ /dev/null
@@ -1 +0,0 @@
-*{margin:0;padding:0}*:focus{outline:none}*::-moz-focus-inner{border:0}html{height:100%}body{background:#282828;font-family:'Lato', sans-serif;font-size:81.25%;min-height:100%;display:flex;flex-direction:column}.container.centered{flex-grow:1;display:flex;margin:0 auto 64px auto;padding:0 16px;flex-direction:column;justify-content:center;width:100%;max-width:640px}.button{display:block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);color:#282828;text-decoration:none;line-height:24px;cursor:pointer;min-width:40px;text-align:center}.button:hover,.button:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.button[aria-disabled=true],.button:disabled{cursor:default;background:#838383;box-shadow:0 -1px 2px rgba(0,0,0,0.1)}nav.navigation{position:sticky;top:0;background:#333333;box-shadow:0 2px 4px rgba(0,0,0,0.2);padding:0 16px;z-index:100}nav.navigation ul{display:flex;max-width:1024px;margin:0 auto;height:56px;align-items:center}nav.navigation ul li{display:block}@media (max-width: 640px){nav.navigation ul li.images,nav.navigation ul li.albums{display:none}}nav.navigation ul li.title a{display:inline-block;line-height:56px}nav.navigation ul li.title a img{width:32px;height:32px;vertical-align:middle}nav.navigation ul li.spacer{flex-grow:1}nav.navigation ul li:not(.spacer){margin:0 8px}nav.navigation ul li:not(.spacer):first-child{margin-left:0}nav.navigation ul li:not(.spacer):last-child{margin-right:0}.page.upload{flex-grow:1;display:flex;flex-direction:column}.page.upload .alert{padding:16px;margin:16px 0;box-shadow:0 2px 4px rgba(33,33,33,0.2);text-decoration:none;border-radius:2px}.page.upload .alert.success{background:#DCEDC8;color:#689F38;border-color:#689F38}.page.upload .alert.success a{color:#33691E}.page.upload .alert.error{background:#FFEBEE;color:#F44336;border-color:#F44336}.page.upload .alert.error a{color:#D32F2F}.page.upload form.upload{padding:96px 0;box-shadow:0 2px 4px rgba(0,0,0,0.2);text-decoration:none;border-radius:2px;text-align:center;background:#333333}.page.upload form.upload .upload-label{font-size:18pt;color:#fff}.page.upload form.upload label{position:relative;display:inline-block;overflow:hidden}.page.upload form.upload label span.text{position:relative;display:inline-block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);line-height:24px;color:#282828;cursor:pointer;z-index:1}.page.upload form.upload label span.text:hover,.page.upload form.upload label span.text:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.page.upload form.upload label input[type=file]{position:absolute;left:0;right:0;top:0;bottom:0;opacity:0}.page.upload .uploading-images{display:flex;flex-direction:row}.page.upload .uploading-images .images{display:flex;flex-direction:column;align-items:stretch;flex-grow:1}.page.upload .uploading-images .images .detail{margin-bottom:32px}.page.upload .uploading-images .images .detail .image{position:relative}.page.upload .uploading-images .images .detail .image .progress{position:absolute;top:-4px;left:0;right:0;height:4px;display:block;background-color:rgba(255,193,7,0.2);overflow:hidden;transition:opacity 400ms}.page.upload .uploading-images .images .detail .image .progress .indeterminate{background-color:rgba(255,193,7,0.8)}.page.upload .uploading-images .images .detail .image .progress .indeterminate::before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.page.upload .uploading-images .images .detail .image .progress .indeterminate::after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation-delay:1.125s}.page.upload .uploading-images .images .detail:not(.uploading) .progress{opacity:0}.page.upload.submitted .container.centered{display:none}.page.upload:not(.submitted) .uploading-images{display:none}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.page.image.detail,.page.upload .uploading-images{display:flex;flex-direction:row;align-items:start;align-self:center;padding:32px 32px 128px;justify-content:center}@media (max-width: 1024px){.page.image.detail,.page.upload .uploading-images{flex-direction:column;padding:32px 0;align-items:stretch;justify-content:start}}.page.image.detail .sidebar,.page.upload .uploading-images .sidebar{width:250px;max-width:640px;margin-left:32px}.page.image.detail .sidebar .url,.page.upload .uploading-images .sidebar .url{background:#333333;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.4);padding:8px;margin-bottom:8px}@media (max-width: 1024px){.page.image.detail .sidebar .url,.page.upload .uploading-images .sidebar .url{padding:16px;margin-bottom:32px}}.page.image.detail .sidebar .url p,.page.upload .uploading-images .sidebar .url p{color:#ffffff}.page.image.detail .sidebar .url div,.page.upload .uploading-images .sidebar .url div{display:flex;flex-direction:row}.page.image.detail .sidebar .url div input,.page.upload .uploading-images .sidebar .url div input{background:none;color:#fff;opacity:0.4;border:none;flex-shrink:1;display:block;flex-grow:1;width:0;text-overflow:ellipsis}.page.image.detail .sidebar .url div button.copy,.page.upload .uploading-images .sidebar .url div button.copy{display:block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);border:none;font-family:'Lato', sans-serif;font-size:10pt;line-height:24px;margin-left:8px;cursor:pointer}.page.image.detail .sidebar .url div button.copy:hover,.page.image.detail .sidebar .url div button.copy:focus,.page.upload .uploading-images .sidebar .url div button.copy:hover,.page.upload .uploading-images .sidebar .url div button.copy:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}.page.image.detail .sidebar .actions,.page.upload .uploading-images .sidebar .actions{background:#333333;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.4);display:flex;flex-direction:row;margin-bottom:8px}@media (max-width: 1024px){.page.image.detail .sidebar .actions,.page.upload .uploading-images .sidebar .actions{margin-bottom:32px}}.page.image.detail .sidebar .actions .delete-form,.page.image.detail .sidebar .actions .update-form,.page.upload .uploading-images .sidebar .actions .delete-form,.page.upload .uploading-images .sidebar .actions .update-form{display:flex;flex-grow:1;flex-basis:0}.page.image.detail .sidebar .actions .delete-form input[type=submit],.page.image.detail .sidebar .actions .update-form input[type=submit],.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit],.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]{display:block;background:#FFC107;padding:4px 16px;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,0.1);border:none;font-family:'Lato', sans-serif;font-size:10pt;line-height:24px;margin:8px;flex-grow:1;cursor:pointer}@media (max-width: 1024px){.page.image.detail .sidebar .actions .delete-form input[type=submit],.page.image.detail .sidebar .actions .update-form input[type=submit],.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit],.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]{margin:16px}}.page.image.detail .sidebar .actions .delete-form input[type=submit]:hover,.page.image.detail .sidebar .actions .delete-form input[type=submit]:focus,.page.image.detail .sidebar .actions .update-form input[type=submit]:hover,.page.image.detail .sidebar .actions .update-form input[type=submit]:focus,.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit]:hover,.page.upload .uploading-images .sidebar .actions .delete-form input[type=submit]:focus,.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]:hover,.page.upload .uploading-images .sidebar .actions .update-form input[type=submit]:focus{background:#FFD54F;box-shadow:0 2px 4px rgba(0,0,0,0.2)}@media (max-width: 1024px){.page.image.detail .sidebar,.page.upload .uploading-images .sidebar{width:auto;margin-left:0;margin-top:32px}}.page.image.detail .detail,.page.upload .uploading-images .detail{max-width:640px;width:100%;background:#333333;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,0.4)}.page.image.detail .detail .title,.page.upload .uploading-images .detail .title{line-height:1.25;padding:16px;vertical-align:middle;font-size:14pt;font-weight:normal;color:#eee;background:none;border:none;width:100%;box-sizing:border-box}.page.image.detail .detail .title:not(.fake-input):empty,.page.upload .uploading-images .detail .title:not(.fake-input):empty{display:none}.page.image.detail .detail .title:not(.fake-input):empty+.image,.page.upload .uploading-images .detail .title:not(.fake-input):empty+.image{border-top-left-radius:4px;border-top-right-radius:4px}.page.image.detail .detail .image,.page.upload .uploading-images .detail .image{background:#000;display:flex;flex-direction:row;justify-content:center;align-items:start}.page.image.detail .detail .image img,.page.upload .uploading-images .detail .image img{max-width:100%}.page.image.detail .detail .description,.page.upload .uploading-images .detail .description{line-height:1.25;padding:16px;vertical-align:middle;font-size:11pt;font-weight:normal;color:#eee;background:none;border:none;width:100%;box-sizing:border-box;font-family:'Lato', sans-serif;resize:vertical;white-space:pre-line}.page.image.detail .detail .description:not(.fake-input):empty,.page.upload .uploading-images .detail .description:not(.fake-input):empty{display:none}.page.image.detail .fake-input[contenteditable]:empty:before,.page.upload .uploading-images .fake-input[contenteditable]:empty:before{opacity:0.4;content:attr(placeholder)}.page.image.list{max-width:1024px;margin:8px auto 48px;display:grid;grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));grid-gap:8px;align-self:stretch;width:calc(100% - 16px)}@media (max-width: 600px){.page.image.list{grid-template-columns:repeat(auto-fill, minmax(160px, 1fr))}}.page.image.list .image{padding:8px;position:relative;box-shadow:0 2px 4px rgba(0,0,0,0.2);transition:all 200ms;text-decoration:none;background:#333333;border-radius:2px;will-change:transform}.page.image.list .image:hover,.page.image.list .image:focus{transform:translate(0, -2px);box-shadow:0 4px 6px rgba(0,0,0,0.4)}.page.image.list .image .image-container{display:flex;justify-content:stretch;align-content:stretch;justify-items:stretch;align-items:stretch;flex-direction:column;background:#000000}.page.image.list .image .image-container img{aspect-ratio:1;object-fit:contain}.page.image.list .image .info{display:block;z-index:1;color:#eeeeee;line-height:1.25;font-size:10pt;padding-top:12px}.page.image.list .image .info p{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.page.image.list .image .info p.title{font-weight:600}.page.image.list .image .info p.title span.placeholder{opacity:0.4}ul.pagination{display:flex;flex-direction:row;background:#333;box-shadow:0 2px 4px rgba(0,0,0,0.2);padding:4px 8px;color:#fff;justify-self:center;grid-column-start:1;grid-column-end:-1;max-width:480px;width:calc(100% - 16px)}ul.pagination li.page{appearance:none;list-style:none;margin:4px;padding:0;line-height:24px}ul.pagination li.page.current{display:block;padding:4px 16px;flex-grow:1;text-align:center}
diff --git a/frontend/assets/favicon.svg b/frontend/assets/favicon.svg
index 15701aabcf0d0f3f014f96832f794fd80e01b80b..50cb23016f1ec61f97df09f92a39b012716761d1 100644
--- a/frontend/assets/favicon.svg
+++ b/frontend/assets/favicon.svg
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1024 1024">
     <defs>
-        <linearGradient id="linearGradient861" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="1024">
-            <stop stop-color="#FFD54F" offset="0"/>
-            <stop stop-color="#FFC107" offset="1"/>
-        </linearGradient>
-        <linearGradient id="linearGradient859" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1024" y2="0">
+        <linearGradient id="background" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1024" y2="0">
             <stop stop-color="#4c4c4c" offset="0"/>
             <stop stop-color="#1a1a1a" offset="1"/>
         </linearGradient>
+        <linearGradient id="foreground" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="1024">
+            <stop stop-color="#FFD54F" offset="0"/>
+            <stop stop-color="#FFC107" offset="1"/>
+        </linearGradient>
     </defs>
     <path d="m 188.74089,189.79585 662.14201,29.73874 c 15.33377,0.68868 27.31132,13.69238 26.61678,28.89711 l -31.56963,691.1239 c -0.69457,15.20473 -13.80866,27.08144 -29.14246,26.39277 L 154.64562,936.2096 c -15.33382,-0.68869 -27.3114,-13.69234 -26.61688,-28.89705 l 31.5697,-691.12389 c 0.69454,-15.20473 13.80865,-27.08149 29.14245,-26.39281 z"
-          fill="url(#linearGradient859)"/>
+          fill="url(#background)"/>
     <path d="m 366.39565,8.0213922 c -0.26427,0 -0.56628,0 -0.83056,0.037434 C 315.01465,13.037623 264.53971,27.37506 192.20614,57.547318 c -5.28534,2.208639 -8.23003,7.861258 -6.94644,13.439008 67.84104,299.102154 120.12809,595.958244 23.55749,930.024374 -2.49166,8.7222 5.39859,16.8829 14.30816,14.6742 90.86998,-22.49812 101.59166,-28.71226 188.04462,-44.77169 5.09657,-0.93585 8.98507,-5.05366 9.58911,-10.1822 14.9877,-124.95656 17.59261,-250.92386 13.10008,-376.10505 109.3309,120.20238 176.37914,231.30817 243.42738,343.27495 2.26514,3.78088 6.49341,6.02696 10.98595,5.72749 65.04735,-4.15524 98.00518,-13.40158 187.7426,-11.56728 9.66462,0.18716 15.51624,-10.51911 10.04213,-18.45525 C 804.73859,786.32339 726.28913,679.14824 616.5807,555.01523 L 854.88652,302.77319 c 5.47409,-5.87722 3.54872,-15.34817 -3.77523,-18.71728 -36.2423,-16.47121 -73.84368,-33.3542 -143.1193,-42.15131 0,0 -7.51274,0.82355 -10.11763,3.55628 L 430.95223,522.25999 c -9.24934,-170.40214 -30.23967,-338.7828 -51.68302,-500.31294 0,-0.224608 -0.0378,-0.48665 -0.11326,-0.748692 l -0.75505,-3.743456 c -1.13257,-5.615184 -6.15363,-9.6206826 -11.9675,-9.4709444 z"
-          fill="url(#linearGradient861)"/>
+          fill="url(#foreground)"/>
 </svg>
diff --git a/frontend/assets/sass/_error.scss b/frontend/assets/sass/_error.scss
new file mode 100644
index 0000000000000000000000000000000000000000..9cabd0747fbb8d9a2697fef7e00852b886580e55
--- /dev/null
+++ b/frontend/assets/sass/_error.scss
@@ -0,0 +1,21 @@
+.container.fatal {
+  padding: 16px;
+  margin: 16px auto;
+  box-shadow: 0 2px 4px rgba(33, 33, 33, 0.2);
+  text-decoration: none;
+  border-radius: 2px;
+  background: #FFEBEE;
+  color: #F44336;
+  border-color: #F44336;
+  font-size: 14px;
+
+  ins {
+    text-decoration: none;
+    opacity: 0.75;
+  }
+
+  code:last-child {
+    display: block;
+    margin-top: 16px;
+  }
+}
diff --git a/frontend/assets/sass/_navigation.sass b/frontend/assets/sass/_navigation.sass
deleted file mode 100644
index 191378da92c5ce48404bd40ea7d0bbe1e4cec595..0000000000000000000000000000000000000000
--- a/frontend/assets/sass/_navigation.sass
+++ /dev/null
@@ -1,34 +0,0 @@
-nav.navigation
-  position: sticky
-  top: 0
-  background: #333333
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-  padding: 0 16px
-  z-index: 100
-  ul
-    display: flex
-    max-width: 1024px
-    margin: 0 auto
-    height: 56px
-    align-items: center
-    li
-      display: block
-      &.images, &.albums
-        @media (max-width: 640px)
-          display: none
-      &.title
-        a
-          display: inline-block
-          line-height: 56px
-          img
-            width: 32px
-            height: 32px
-            vertical-align: middle
-      &.spacer
-        flex-grow: 1
-      &:not(.spacer)
-        margin: 0 8px
-        &:first-child
-          margin-left: 0
-        &:last-child
-          margin-right: 0
diff --git a/frontend/assets/sass/_navigation.scss b/frontend/assets/sass/_navigation.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d91ee734db041511de9e2d5d1904f6cd439a6068
--- /dev/null
+++ b/frontend/assets/sass/_navigation.scss
@@ -0,0 +1,56 @@
+nav.navigation {
+  position: sticky;
+  top: 0;
+  background: #333333;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+  padding: 0 16px;
+  z-index: 100;
+
+  ul {
+    display: flex;
+    max-width: 1024px;
+    margin: 0 auto;
+    height: 56px;
+    align-items: center;
+
+    li {
+      display: block;
+
+      &.images,
+      &.albums {
+        @media (max-width: 640px) {
+          display: none;
+        }
+      }
+
+      &.title {
+        a {
+          display: inline-block;
+          line-height: 56px;
+
+          img {
+            width: 32px;
+            height: 32px;
+            vertical-align: middle;
+          }
+        }
+      }
+
+      &.spacer {
+        flex-grow: 1;
+      }
+
+      &:not(.spacer) {
+        margin: 0 8px;
+
+        &:first-child {
+          margin-left: 0;
+        }
+
+        &:last-child {
+          margin-right: 0;
+        }
+      }
+    }
+  }
+}
diff --git a/frontend/assets/sass/_page_image_detail.sass b/frontend/assets/sass/_page_image_detail.sass
deleted file mode 100644
index e1bccc7cc4069478b5aedff2730040af91321639..0000000000000000000000000000000000000000
--- a/frontend/assets/sass/_page_image_detail.sass
+++ /dev/null
@@ -1,139 +0,0 @@
-.page.image.detail, .page.upload .uploading-images
-  display: flex
-  flex-direction: row
-  align-items: start
-  align-self: center
-  padding: 32px 32px 128px
-  justify-content: center
-  @media (max-width: 1024px)
-    flex-direction: column
-    padding: 32px 0
-    align-items: stretch
-    justify-content: start
-  .sidebar
-    width: 250px
-    max-width: 640px
-    margin-left: 32px
-    .url
-      background: #333333
-      border-radius: 4px
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4)
-      padding: 8px
-      margin-bottom: 8px
-      @media (max-width: 1024px)
-        padding: 16px
-        margin-bottom: 32px
-      p
-        color: #ffffff
-      div
-        display: flex
-        flex-direction: row
-        input
-          background: none
-          color: #fff
-          opacity: 0.4
-          border: none
-          flex-shrink: 1
-          display: block
-          flex-grow: 1
-          width: 0
-          text-overflow: ellipsis
-        button.copy
-          display: block
-          background: #FFC107
-          padding: 4px 16px
-          border-radius: 2px
-          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)
-          border: none
-          font-family: 'Lato', sans-serif
-          font-size: 10pt
-          line-height: 24px
-          margin-left: 8px
-          cursor: pointer
-          &:hover, &:focus
-            background: #FFD54F
-            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-    .actions
-      background: #333333
-      border-radius: 4px
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4)
-      display: flex
-      flex-direction: row
-      margin-bottom: 8px
-      @media (max-width: 1024px)
-        margin-bottom: 32px
-      .delete-form, .update-form
-        display: flex
-        flex-grow: 1
-        flex-basis: 0
-        input[type=submit]
-          display: block
-          background: #FFC107
-          padding: 4px 16px
-          border-radius: 2px
-          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)
-          border: none
-          font-family: 'Lato', sans-serif
-          font-size: 10pt
-          line-height: 24px
-          margin: 8px
-          flex-grow: 1
-          cursor: pointer
-          @media (max-width: 1024px)
-            margin: 16px
-          &:hover, &:focus
-            background: #FFD54F
-            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-    @media (max-width: 1024px)
-      width: auto
-      margin-left: 0
-      margin-top: 32px
-  .detail
-    max-width: 640px
-    width: 100%
-    background: #333333
-    border-radius: 4px
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4)
-    .title
-      line-height: 1.25
-      padding: 16px
-      vertical-align: middle
-      font-size: 14pt
-      font-weight: normal
-      color: #eee
-      background: none
-      border: none
-      width: 100%
-      box-sizing: border-box
-      &:not(.fake-input):empty
-        display: none
-        & + .image
-          border-top-left-radius: 4px
-          border-top-right-radius: 4px
-    .image
-      background: #000
-      display: flex
-      flex-direction: row
-      justify-content: center
-      align-items: start
-      img
-        max-width: 100%
-    .description
-      line-height: 1.25
-      padding: 16px
-      vertical-align: middle
-      font-size: 11pt
-      font-weight: normal
-      color: #eee
-      background: none
-      border: none
-      width: 100%
-      box-sizing: border-box
-      font-family: 'Lato', sans-serif
-      resize: vertical
-      white-space: pre-line
-      &:not(.fake-input):empty
-        display: none
-  .fake-input[contenteditable]:empty:before
-    opacity: 0.4
-    content: attr(placeholder)
\ No newline at end of file
diff --git a/frontend/assets/sass/_page_image_detail.scss b/frontend/assets/sass/_page_image_detail.scss
new file mode 100644
index 0000000000000000000000000000000000000000..5cd28778f330796a1dfdf660bb258a72a6d2f9d2
--- /dev/null
+++ b/frontend/assets/sass/_page_image_detail.scss
@@ -0,0 +1,189 @@
+.page.image.detail, .page.upload .uploading-images {
+  display: flex;
+  flex-direction: row;
+  align-items: start;
+  align-self: center;
+  padding: 32px 32px 128px;
+  justify-content: center;
+  @media (max-width: 1024px) {
+    flex-direction: column;
+    padding: 32px 0;
+    align-items: stretch;
+    justify-content: start;
+  }
+
+  .sidebar {
+    width: 250px;
+    max-width: 640px;
+    margin-left: 32px;
+
+    .url {
+      background: #333333;
+      border-radius: 4px;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
+      padding: 8px;
+      margin-bottom: 8px;
+      @media (max-width: 1024px) {
+        padding: 16px;
+        margin-bottom: 32px;
+      }
+
+      p {
+        color: #ffffff;
+      }
+
+      div {
+        display: flex;
+        flex-direction: row;
+
+        input {
+          background: none;
+          color: #fff;
+          opacity: 0.4;
+          border: none;
+          flex-shrink: 1;
+          display: block;
+          flex-grow: 1;
+          width: 0;
+          text-overflow: ellipsis;
+        }
+
+        button.copy {
+          display: block;
+          background: #FFC107;
+          padding: 4px 16px;
+          border-radius: 2px;
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+          border: none;
+          font-family: 'Lato', sans-serif;
+          font-size: 10pt;
+          line-height: 24px;
+          margin-left: 8px;
+          cursor: pointer;
+
+          &:hover,
+          &:focus {
+            background: #FFD54F;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+          }
+        }
+      }
+    }
+
+    .actions {
+      background: #333333;
+      border-radius: 4px;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
+      display: flex;
+      flex-direction: row;
+      margin-bottom: 8px;
+      @media (max-width: 1024px) {
+        margin-bottom: 32px;
+      }
+
+      .delete-form,
+      .update-form {
+        display: flex;
+        flex-grow: 1;
+        flex-basis: 0;
+
+        input[type=submit] {
+          display: block;
+          background: #FFC107;
+          padding: 4px 16px;
+          border-radius: 2px;
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+          border: none;
+          font-family: 'Lato', sans-serif;
+          font-size: 10pt;
+          line-height: 24px;
+          margin: 8px;
+          flex-grow: 1;
+          cursor: pointer;
+          @media (max-width: 1024px) {
+            margin: 16px;
+          }
+
+          &:hover,
+          &:focus {
+            background: #FFD54F;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+          }
+        }
+      }
+    }
+
+    @media (max-width: 1024px) {
+      width: auto;
+      margin-left: 0;
+      margin-top: 32px;
+    }
+  }
+
+  .detail {
+    max-width: 640px;
+    width: 100%;
+    background: #333333;
+    border-radius: 4px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
+
+    .title {
+      line-height: 1.25;
+      padding: 16px;
+      vertical-align: middle;
+      font-size: 14pt;
+      font-weight: normal;
+      color: #eee;
+      background: none;
+      border: none;
+      width: 100%;
+      box-sizing: border-box;
+
+      &:not(.fake-input):empty {
+        display: none;
+
+        & + .image {
+          border-top-left-radius: 4px;
+          border-top-right-radius: 4px;
+        }
+      }
+    }
+
+    .image {
+      background: #000;
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+      align-items: start;
+
+      img {
+        max-width: 100%;
+      }
+    }
+
+    .description {
+      line-height: 1.25;
+      padding: 16px;
+      vertical-align: middle;
+      font-size: 11pt;
+      font-weight: normal;
+      color: #eee;
+      background: none;
+      border: none;
+      width: 100%;
+      box-sizing: border-box;
+      font-family: 'Lato', sans-serif;
+      resize: vertical;
+      white-space: pre-line;
+
+      &:not(.fake-input):empty {
+        display: none;
+      }
+    }
+  }
+
+  .fake-input[contenteditable]:empty:before {
+    opacity: 0.4;
+    content: attr(placeholder);
+  }
+}
diff --git a/frontend/assets/sass/_page_image_list.sass b/frontend/assets/sass/_page_image_list.sass
deleted file mode 100644
index e367c712c7802a8beed80def31f18065d5f000b0..0000000000000000000000000000000000000000
--- a/frontend/assets/sass/_page_image_list.sass
+++ /dev/null
@@ -1,55 +0,0 @@
-.page.image.list
-  max-width: 1024px
-  margin: 8px auto 48px
-  display: grid
-  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))
-  grid-gap: 8px
-  align-self: stretch
-  width: calc(100% - 16px)
-
-  @media (max-width: 600px)
-    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr))
-
-  .image
-    padding: 8px
-    position: relative
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-    transition: all 200ms
-    text-decoration: none
-    background: #333333
-    border-radius: 2px
-    will-change: transform
-
-    &:hover, &:focus
-      transform: translate(0, -2px)
-      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4)
-    .image-container
-      display: flex
-      justify-content: stretch
-      align-content: stretch
-      justify-items: stretch
-      align-items: stretch
-      flex-direction: column
-      background: #000000
-
-      img
-        aspect-ratio: 1
-        object-fit: contain
-    .info
-      display: block
-      z-index: 1
-      color: #eeeeee
-      line-height: 1.25
-      font-size: 10pt
-      padding-top: 12px
-
-      p
-        white-space: nowrap
-        text-overflow: ellipsis
-        overflow: hidden
-
-        &.title
-          font-weight: 600
-
-          span.placeholder
-            opacity: 0.4
diff --git a/frontend/assets/sass/_page_image_list.scss b/frontend/assets/sass/_page_image_list.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7dbe2be907eed72ee821f1d65d3e8890e5e8a564
--- /dev/null
+++ b/frontend/assets/sass/_page_image_list.scss
@@ -0,0 +1,68 @@
+.page.image.list {
+  max-width: 1024px;
+  margin: 8px auto 48px;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+  grid-gap: 8px;
+  align-self: stretch;
+  width: calc(100% - 16px);
+
+  @media (max-width: 600px) {
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+  }
+
+  .image {
+    padding: 8px;
+    position: relative;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+    transition: all 200ms;
+    text-decoration: none;
+    background: #333333;
+    border-radius: 2px;
+    will-change: transform;
+
+    &:hover,
+    &:focus {
+      transform: translate(0, -2px);
+      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
+    }
+
+    .image-container {
+      display: flex;
+      justify-content: stretch;
+      align-content: stretch;
+      justify-items: stretch;
+      align-items: stretch;
+      flex-direction: column;
+      background: #000000;
+
+      img {
+        aspect-ratio: 1;
+        object-fit: contain;
+      }
+    }
+
+    .info {
+      display: block;
+      z-index: 1;
+      color: #eeeeee;
+      line-height: 1.25;
+      font-size: 10pt;
+      padding-top: 12px;
+
+      p {
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+
+        &.title {
+          font-weight: 600;
+
+          span.placeholder {
+            opacity: 0.4;
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/frontend/assets/sass/_page_upload.sass b/frontend/assets/sass/_page_upload.sass
deleted file mode 100644
index a82a5c84600fcbc426eec2f3c6cd5af71c2802c3..0000000000000000000000000000000000000000
--- a/frontend/assets/sass/_page_upload.sass
+++ /dev/null
@@ -1,130 +0,0 @@
-.page.upload
-  flex-grow: 1
-  display: flex
-  flex-direction: column
-
-  .alert
-    padding: 16px
-    margin: 16px 0
-    box-shadow: 0 2px 4px rgba(33, 33, 33, 0.2)
-    text-decoration: none
-    border-radius: 2px
-    &.success
-      background: #DCEDC8
-      color: #689F38
-      border-color: #689F38
-      a
-        color: #33691E
-    &.error
-      background: #FFEBEE
-      color: #F44336
-      border-color: #F44336
-      a
-        color: #D32F2F
-
-  form.upload
-    padding: 96px 0
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-    text-decoration: none
-    border-radius: 2px
-    text-align: center
-    background: #333333
-    .upload-label
-      font-size: 18pt
-      color: #fff
-    label
-      position: relative
-      display: inline-block
-      overflow: hidden
-      span.text
-        position: relative
-        display: inline-block
-        background: #FFC107
-        padding: 4px 16px
-        border-radius: 2px
-        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)
-        line-height: 24px
-        color: #282828
-        cursor: pointer
-        z-index: 1
-        &:hover, &:focus
-          background: #FFD54F
-          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-      input[type=file]
-        position: absolute
-        left: 0
-        right: 0
-        top: 0
-        bottom: 0
-        opacity: 0
-  .uploading-images
-    display: flex
-    flex-direction: row
-    .images
-      display: flex
-      flex-direction: column
-      align-items: stretch
-      flex-grow: 1
-      .detail
-        margin-bottom: 32px
-        .image
-          position: relative
-          .progress
-            position: absolute
-            top: -4px
-            left: 0
-            right: 0
-            height: 4px
-            display: block
-            background-color: rgba(255, 193, 7, 0.2)
-            overflow: hidden
-            transition: opacity 400ms
-            .indeterminate
-              background-color: rgba(255, 193, 7, 0.8)
-              &::before
-                content: ''
-                position: absolute
-                background-color: inherit
-                top: 0
-                left: 0
-                bottom: 0
-                will-change: left, right
-                animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite
-              &::after
-                content: ''
-                position: absolute
-                background-color: inherit
-                top: 0
-                left: 0
-                bottom: 0
-                will-change: left, right
-                animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite
-                animation-delay: 1.125s
-        &:not(.uploading) .progress
-          opacity: 0
-  &.submitted .container.centered
-    display: none
-  &:not(.submitted) .uploading-images
-    display: none
-
-@keyframes indeterminate
-  0%
-    left: -35%
-    right: 100%
-  60%
-    left: 100%
-    right: -90%
-  100%
-    left: 100%
-    right: -90%
-
-@keyframes indeterminate-short
-  0%
-    left: -200%
-    right: 100%
-  60%
-    left: 107%
-    right: -8%
-  100%
-    left: 107%
-    right: -8%
\ No newline at end of file
diff --git a/frontend/assets/sass/_page_upload.scss b/frontend/assets/sass/_page_upload.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c1353c79cdf93a9c434324b27fcb6fc65d097b85
--- /dev/null
+++ b/frontend/assets/sass/_page_upload.scss
@@ -0,0 +1,184 @@
+.page.upload {
+  flex-grow: 1;
+  display: flex;
+  flex-direction: column;
+
+  .alert {
+    padding: 16px;
+    margin: 16px 0;
+    box-shadow: 0 2px 4px rgba(33, 33, 33, 0.2);
+    text-decoration: none;
+    border-radius: 2px;
+
+    &.success {
+      background: #DCEDC8;
+      color: #689F38;
+      border-color: #689F38;
+
+      a {
+        color: #33691E;
+      }
+    }
+
+    &.error {
+      background: #FFEBEE;
+      color: #F44336;
+      border-color: #F44336;
+
+      a {
+        color: #D32F2F;
+      }
+    }
+  }
+
+  form.upload {
+    padding: 96px 0;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+    text-decoration: none;
+    border-radius: 2px;
+    text-align: center;
+    background: #333333;
+
+    .upload-label {
+      font-size: 18pt;
+      color: #fff;
+    }
+
+    label {
+      position: relative;
+      display: inline-block;
+      overflow: hidden;
+
+      span.text {
+        position: relative;
+        display: inline-block;
+        background: #FFC107;
+        padding: 4px 16px;
+        border-radius: 2px;
+        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+        line-height: 24px;
+        color: #282828;
+        cursor: pointer;
+        z-index: 1;
+
+        &:hover,
+        &:focus {
+          background: #FFD54F;
+          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+        }
+      }
+
+      input[type=file] {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        opacity: 0;
+      }
+    }
+  }
+
+  .uploading-images {
+    display: flex;
+    flex-direction: row;
+
+    .images {
+      display: flex;
+      flex-direction: column;
+      align-items: stretch;
+      flex-grow: 1;
+
+      .detail {
+        margin-bottom: 32px;
+
+        .image {
+          position: relative;
+
+          .progress {
+            position: absolute;
+            top: -4px;
+            left: 0;
+            right: 0;
+            height: 4px;
+            display: block;
+            background-color: rgba(255, 193, 7, 0.2);
+            overflow: hidden;
+            transition: opacity 400ms;
+
+            .indeterminate {
+              background-color: rgba(255, 193, 7, 0.8);
+
+              &::before {
+                content: '';
+                position: absolute;
+                background-color: inherit;
+                top: 0;
+                left: 0;
+                bottom: 0;
+                will-change: left, right;
+                animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
+              }
+
+              &::after {
+                content: '';
+                position: absolute;
+                background-color: inherit;
+                top: 0;
+                left: 0;
+                bottom: 0;
+                will-change: left, right;
+                animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
+                animation-delay: 1.125s;
+              }
+            }
+          }
+        }
+
+        &:not(.uploading) .progress {
+          opacity: 0;
+        }
+      }
+    }
+  }
+
+  &.submitted .container.centered {
+    display: none;
+  }
+
+  &:not(.submitted) .uploading-images {
+    display: none;
+  }
+}
+
+@keyframes indeterminate {
+  0% {
+    left: -35%;
+    right: 100%;
+  }
+  60% {
+    left: 100%;
+    right: -90%;
+  }
+  100% {
+    left: 100%;
+    right: -90%;
+  }
+}
+
+@keyframes indeterminate-short {
+  0% {
+    left: -200%;
+    right: 100%;
+  }
+  60% {
+    left: 107%;
+    right: -8%;
+  }
+  100% {
+    left: 107%;
+    right: -8%;
+  }
+}
+
+;
diff --git a/frontend/assets/sass/_pagination.sass b/frontend/assets/sass/_pagination.sass
deleted file mode 100644
index 9d206c6029197de3b8b1cb500d7d34f4f7dc64f4..0000000000000000000000000000000000000000
--- a/frontend/assets/sass/_pagination.sass
+++ /dev/null
@@ -1,25 +0,0 @@
-ul.pagination
-  display: flex
-  flex-direction: row
-  background: #333
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-  padding: 4px 8px
-  color: #fff
-  justify-self: center
-  grid-column-start: 1
-  grid-column-end: -1
-  max-width: 480px
-  width: calc(100% - 16px)
-
-  li.page
-    appearance: none
-    list-style: none
-    margin: 4px
-    padding: 0
-    line-height: 24px
-
-    &.current
-      display: block
-      padding: 4px 16px
-      flex-grow: 1
-      text-align: center
diff --git a/frontend/assets/sass/_pagination.scss b/frontend/assets/sass/_pagination.scss
new file mode 100644
index 0000000000000000000000000000000000000000..51cd7c291e354cfb5c726fd64c1d224f6200a409
--- /dev/null
+++ b/frontend/assets/sass/_pagination.scss
@@ -0,0 +1,28 @@
+ul.pagination {
+  display: flex;
+  flex-direction: row;
+  background: #333;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+  padding: 4px 8px;
+  color: #fff;
+  justify-self: center;
+  grid-column-start: 1;
+  grid-column-end: -1;
+  max-width: 480px;
+  width: calc(100% - 16px);
+
+  li.page {
+    appearance: none;
+    list-style: none;
+    margin: 4px;
+    padding: 0;
+    line-height: 24px;
+
+    &.current {
+      display: block;
+      padding: 4px 16px;
+      flex-grow: 1;
+      text-align: center;
+    }
+  }
+}
diff --git a/frontend/assets/sass/fonts.sass b/frontend/assets/sass/fonts.scss
similarity index 50%
rename from frontend/assets/sass/fonts.sass
rename to frontend/assets/sass/fonts.scss
index e0b855b982e0cf6f578c7dba58d98a41cf01e640..5fefcf5dbd449ebef0e788896e05c1fa333e92a1 100644
--- a/frontend/assets/sass/fonts.sass
+++ b/frontend/assets/sass/fonts.scss
@@ -1,31 +1,37 @@
-@font-face
-  font-family: 'Lato'
-  src: url('/assets/fonts/Lato-Regular.eot')
-  src: url('/assets/fonts/Lato-Regular.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-Regular.woff2') format('woff2'), url('/assets/fonts/Lato-Regular.woff') format('woff'), url('/assets/fonts/Lato-Regular.ttf') format('truetype')
-  font-style: normal
-  font-weight: normal
-  text-rendering: optimizeLegibility
+@font-face {
+  font-family: 'Lato';
+  src: url('/assets/fonts/Lato-Regular.eot');
+  src: url('/assets/fonts/Lato-Regular.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-Regular.woff2') format('woff2'), url('/assets/fonts/Lato-Regular.woff') format('woff'), url('/assets/fonts/Lato-Regular.ttf') format('truetype');
+  font-style: normal;
+  font-weight: normal;
+  text-rendering: optimizeLegibility;
+}
 
-@font-face
-  font-family: 'Lato'
-  src: url('/assets/fonts/Lato-Bold.eot')
-  src: url('/assets/fonts/Lato-Bold.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-Bold.woff2') format('woff2'), url('/assets/fonts/Lato-Bold.woff') format('woff'), url('/assets/fonts/Lato-Bold.ttf') format('truetype')
-  font-style: normal
-  font-weight: bold
-  text-rendering: optimizeLegibility
+@font-face {
+  font-family: 'Lato';
+  src: url('/assets/fonts/Lato-Bold.eot');
+  src: url('/assets/fonts/Lato-Bold.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-Bold.woff2') format('woff2'), url('/assets/fonts/Lato-Bold.woff') format('woff'), url('/assets/fonts/Lato-Bold.ttf') format('truetype');
+  font-style: normal;
+  font-weight: bold;
+  text-rendering: optimizeLegibility;
+}
 
-@font-face
-  font-family: 'Lato'
-  src: url('/assets/fonts/Lato-BoldItalic.eot')
-  src: url('/assets/fonts/Lato-BoldItalic.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-BoldItalic.woff2') format('woff2'), url('/assets/fonts/Lato-BoldItalic.woff') format('woff'), url('/assets/fonts/Lato-BoldItalic.ttf') format('truetype')
-  font-style: italic
-  font-weight: bold
-  text-rendering: optimizeLegibility
+@font-face {
+  font-family: 'Lato';
+  src: url('/assets/fonts/Lato-BoldItalic.eot');
+  src: url('/assets/fonts/Lato-BoldItalic.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-BoldItalic.woff2') format('woff2'), url('/assets/fonts/Lato-BoldItalic.woff') format('woff'), url('/assets/fonts/Lato-BoldItalic.ttf') format('truetype');
+  font-style: italic;
+  font-weight: bold;
+  text-rendering: optimizeLegibility;
+}
 
-@font-face
-  font-family: 'Lato'
-  src: url('/assets/fonts/Lato-Italic.eot')
-  src: url('/assets/fonts/Lato-Italic.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-Italic.woff2') format('woff2'), url('/assets/fonts/Lato-Italic.woff') format('woff'), url('/assets/fonts/Lato-Italic.ttf') format('truetype')
-  font-style: italic
-  font-weight: normal
-  text-rendering: optimizeLegibility
\ No newline at end of file
+@font-face {
+  font-family: 'Lato';
+  src: url('/assets/fonts/Lato-Italic.eot');
+  src: url('/assets/fonts/Lato-Italic.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Lato-Italic.woff2') format('woff2'), url('/assets/fonts/Lato-Italic.woff') format('woff'), url('/assets/fonts/Lato-Italic.ttf') format('truetype');
+  font-style: italic;
+  font-weight: normal;
+  text-rendering: optimizeLegibility;
+}
+
+;
diff --git a/frontend/assets/sass/style.sass b/frontend/assets/sass/style.sass
deleted file mode 100644
index b715ca5c808cf9b282ea6612e61540bd7575c0eb..0000000000000000000000000000000000000000
--- a/frontend/assets/sass/style.sass
+++ /dev/null
@@ -1,58 +0,0 @@
-*
-  margin: 0
-  padding: 0
-
-*:focus
-  outline: none
-
-*::-moz-focus-inner
-  border: 0
-
-html
-  height: 100%
-
-body
-  background: #282828
-  font-family: 'Lato', sans-serif
-  font-size: 81.25%
-  min-height: 100%
-  display: flex
-  flex-direction: column
-
-.container.centered
-  flex-grow: 1
-  display: flex
-  margin: 0 auto 64px auto
-  padding: 0 16px
-  flex-direction: column
-  justify-content: center
-  width: 100%
-  max-width: 640px
-
-.button
-  display: block
-  background: #FFC107
-  padding: 4px 16px
-  border-radius: 2px
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)
-  color: #282828
-  text-decoration: none
-  line-height: 24px
-  cursor: pointer
-  min-width: 40px
-  text-align: center
-
-  &:hover, &:focus
-    background: #FFD54F
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
-
-  &[aria-disabled=true], &:disabled
-    cursor: default
-    background: #838383
-    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1)
-
-@import "navigation"
-@import "page_upload"
-@import "page_image_detail"
-@import "page_image_list"
-@import "pagination"
diff --git a/frontend/assets/sass/style.scss b/frontend/assets/sass/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e6d37ba05d97ee5864fca7b68abbca4592e8fa05
--- /dev/null
+++ b/frontend/assets/sass/style.scss
@@ -0,0 +1,70 @@
+* {
+  margin: 0;
+  padding: 0;
+}
+
+*:focus {
+  outline: none;
+}
+
+*::-moz-focus-inner {
+  border: 0;
+}
+
+html {
+  height: 100%;
+}
+
+body {
+  background: #282828;
+  font-family: 'Lato', sans-serif;
+  font-size: 81.25%;
+  min-height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.container.centered {
+  flex-grow: 1;
+  display: flex;
+  margin: 0 auto 64px auto;
+  padding: 0 16px;
+  flex-direction: column;
+  justify-content: center;
+  width: 100%;
+  max-width: 640px;
+}
+
+.button {
+  display: block;
+  background: #FFC107;
+  padding: 4px 16px;
+  border-radius: 2px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+  color: #282828;
+  text-decoration: none;
+  line-height: 24px;
+  cursor: pointer;
+  min-width: 40px;
+  text-align: center;
+
+  &:hover,
+  &:focus {
+    background: #FFD54F;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+  }
+
+  &[aria-disabled=true],
+  &:disabled {
+    cursor: default;
+    background: #838383;
+    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1);
+  }
+}
+
+@import "navigation";
+@import "page_upload";
+@import "page_image_detail";
+@import "page_image_list";
+@import "pagination";
+@import "error";
diff --git a/frontend/errors.go b/frontend/errors.go
index 29c96ff8fddb13183833358a260bb30bed16fca8..e8d868d8da131eb04e8586b78cc319d6c5574f7a 100644
--- a/frontend/errors.go
+++ b/frontend/errors.go
@@ -19,8 +19,8 @@ type errorDto struct {
 }
 
 func formatError(w http.ResponseWriter, data ErrorData, format string) {
-	if data.Code != 0 {
-		data.Code = 500
+	if data.Code == 0 {
+		data.Code = http.StatusInternalServerError
 	}
 	log.Printf(
 		"A type %d error occured for user %s while accessing %s: %s",
@@ -32,7 +32,7 @@ func formatError(w http.ResponseWriter, data ErrorData, format string) {
 	w.WriteHeader(data.Code)
 	if format == "html" {
 		if err := formatTemplate(w, "error.html", data); err != nil {
-			log.Printf("Error while serving html error for %s", data.URL.Path)
+			log.Printf("error serving html error for %s", data.URL.Path)
 			return
 		}
 	} else if format == "json" {
@@ -40,7 +40,7 @@ func formatError(w http.ResponseWriter, data ErrorData, format string) {
 			data.URL.Path,
 			data.Error.Error(),
 		}); err != nil {
-			log.Printf("Error while serving json error for %s", data.URL.Path)
+			log.Printf("error serving json error for %s", data.URL.Path)
 			return
 		}
 	}
diff --git a/frontend/main.go b/frontend/main.go
index 28d6751e5e34c57b0eead624c128e411dfc0b009..df215d81395220fea8f6a7831a469effafb07ca6 100644
--- a/frontend/main.go
+++ b/frontend/main.go
@@ -14,13 +14,13 @@ import (
 func main() {
 	configFile, err := os.Open("config.yaml")
 	if err != nil {
-		log.Fatalf("Could not open config file: %s", err.Error())
+		log.Fatalf("error opening config file: %s", err.Error())
 	}
 	config := shared.LoadConfigFromFile(configFile)
 
 	db, err := sql.Open(config.Database.Format, config.Database.Url)
 	if err != nil {
-		panic(err)
+		log.Fatalf("error connecting to database: %s", err.Error())
 	}
 
 	pageContext := PageContext{
@@ -46,8 +46,7 @@ func main() {
 		w.Write([]byte("OK"))
 	})
 
-	err = http.ListenAndServe(":8080", nil)
-	if err != nil {
-		panic(err)
+	if err := http.ListenAndServe(":8080", nil); err != nil {
+		log.Fatalf("error in http server: %s", err.Error())
 	}
 }
diff --git a/frontend/page_album_detail.go b/frontend/page_album_detail.go
index 94d4683964a011e47794b333d99ea78f2adbf5cf..4308e3ec4de0d33472cf1e64623a673df10f5086 100644
--- a/frontend/page_album_detail.go
+++ b/frontend/page_album_detail.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"fmt"
 	"net/http"
 	"path"
 )
@@ -28,7 +27,8 @@ func pageAlbumDetail(ctx PageContext) http.Handler {
 			WHERE id = $1
 			`, albumId)
 		if err != nil {
-			panic(err)
+			formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
+			return
 		}
 
 		var info Album
@@ -36,7 +36,8 @@ func pageAlbumDetail(ctx PageContext) http.Handler {
 			var owner string
 			err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt)
 			if err != nil {
-				panic(err)
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
+				return
 			}
 
 			result, err := ctx.Database.Query(`
@@ -50,14 +51,16 @@ func pageAlbumDetail(ctx PageContext) http.Handler {
 			ORDER BY position ASC
 			`, albumId)
 			if err != nil {
-				panic(err)
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
+				return
 			}
 
 			for result.Next() {
 				var image AlbumImage
 				err := result.Scan(&image.Id, &owner, &image.Title, &image.Description, &image.Position)
 				if err != nil {
-					panic(err)
+					formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
+					return
 				}
 
 				info.Images = append(info.Images, image)
@@ -68,13 +71,16 @@ func pageAlbumDetail(ctx PageContext) http.Handler {
 				info,
 				owner == user.Id,
 			}); err != nil {
-				panic(err)
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
+				return
 			}
 
 			return
 		}
 
-		w.WriteHeader(http.StatusNotFound)
-		fmt.Fprint(w, "Album not found")
+		if err := returnError(w, http.StatusNotFound, "Image Not Found"); err != nil {
+			formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
+			return
+		}
 	})
 }
diff --git a/frontend/page_image_detail.go b/frontend/page_image_detail.go
index e14ff3f85243ed5dd8ff0e5a73b1e9bd3a67132a..81bd483e9097df38505e84ad5f012c8129697f35 100644
--- a/frontend/page_image_detail.go
+++ b/frontend/page_image_detail.go
@@ -33,7 +33,7 @@ func pageImageDetail(ctx PageContext) http.Handler {
 			WHERE id = $1
 			`, imageId)
 		if err != nil {
-			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 			return
 		}
 
@@ -42,7 +42,7 @@ func pageImageDetail(ctx PageContext) http.Handler {
 		if result.Next() {
 			var owner string
 			if err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 				return
 			}
 
@@ -55,12 +55,12 @@ func pageImageDetail(ctx PageContext) http.Handler {
 					info.Id,
 					user.Id,
 				); err != nil {
-					formatError(w, ErrorData{500, user, r.URL, err}, "html")
+					formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 					return
 				}
 				if r.PostFormValue("from_js") == "true" {
 					if err := returnJson(w, true); err != nil {
-						formatError(w, ErrorData{500, user, r.URL, err}, "html")
+						formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 						return
 					}
 				} else {
@@ -69,12 +69,12 @@ func pageImageDetail(ctx PageContext) http.Handler {
 				return
 			case "delete":
 				if _, err := ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id); err != nil {
-					formatError(w, ErrorData{500, user, r.URL, err}, "html")
+					formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 					return
 				}
 				for _, definition := range ctx.Config.Sizes {
 					if err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix))); err != nil {
-						formatError(w, ErrorData{500, user, r.URL, err}, "html")
+						formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 						return
 					}
 				}
@@ -87,14 +87,14 @@ func pageImageDetail(ctx PageContext) http.Handler {
 				info,
 				owner == user.Id,
 			}); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 				return
 			}
 			return
 		}
 
 		if err := returnError(w, http.StatusNotFound, "Image Not Found"); err != nil {
-			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 			return
 		}
 	})
diff --git a/frontend/page_image_list.go b/frontend/page_image_list.go
index 41771004525eff1237d84fc237bc1eb43cf64224..aa0ab6d7a4504becead8d91fecd94de41e5c2c72 100644
--- a/frontend/page_image_list.go
+++ b/frontend/page_image_list.go
@@ -68,7 +68,7 @@ func pageImageList(ctx PageContext) http.Handler {
 			PageSize,
 		)
 		if err != nil {
-			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 			return
 		}
 
@@ -76,7 +76,7 @@ func pageImageList(ctx PageContext) http.Handler {
 		for result.Next() {
 			var info shared.Image
 			if err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 				return
 			}
 			images = append(images, info)
@@ -89,7 +89,7 @@ func pageImageList(ctx PageContext) http.Handler {
 			pageNumber,
 			pageNumber + 1,
 		}); err != nil {
-			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 			return
 		}
 	})
diff --git a/frontend/page_upload.go b/frontend/page_upload.go
index eae13b58867fd7c25434ed30fe785f59d38847da..0387c251f25f1aa75a3e6723f96fd5191cec08f1 100644
--- a/frontend/page_upload.go
+++ b/frontend/page_upload.go
@@ -80,23 +80,23 @@ func pageUpload(ctx PageContext) http.Handler {
 
 			err := r.ParseMultipartForm(32 << 20)
 			if err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 
 			file, header, err := r.FormFile("file")
 			if err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 			image, err := createImage(ctx.Config, file, header)
 			if err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 
 			if _, err = ctx.Database.Exec("INSERT INTO images (id, owner, created_at, updated_at, original_name, type) VALUES ($1, $2, $3, $4, $5, $6)", image.Id, user.Id, image.CreatedAt, image.CreatedAt, image.OriginalName, image.MimeType); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 
@@ -104,25 +104,25 @@ func pageUpload(ctx PageContext) http.Handler {
 			t, err := shared.NewImageResizeTask(image.Id)
 			fmt.Printf("Submitted task %s at %d\n", image.Id, time.Now().Unix())
 			if err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 			info, err := ctx.Async.Enqueue(t)
 			if err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 			if err := waitOnTask(info, ctx.UploadTimeout); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 			var result shared.Result
 			if err := json.Unmarshal(info.Result, &result); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 			if err = returnJson(w, result); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "json")
 				return
 			}
 			return
@@ -131,7 +131,7 @@ func pageUpload(ctx PageContext) http.Handler {
 			if err := formatTemplate(w, "upload.html", IndexData{
 				user,
 			}); err != nil {
-				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				formatError(w, ErrorData{http.StatusInternalServerError, user, r.URL, err}, "html")
 				return
 			}
 		}
diff --git a/go.mod b/go.mod
index e79686c0015aa75dd366fddb77a79d68d8b781e9..a47887b505b089205226453424fc28298f538745 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module git.kuschku.de/justjanne/imghost-frontend
+module git.kuschku.de/justjanne/imghost
 
 go 1.15
 
diff --git a/shared/config.go b/shared/config.go
index 8348eb5ec58231bf9798b21acda5d3109b4ea1a8..25928b89941c092dd68e9e723249a7155930a1c1 100644
--- a/shared/config.go
+++ b/shared/config.go
@@ -39,7 +39,7 @@ type Config struct {
 func LoadConfigFromFile(file *os.File) Config {
 	var config Config
 	if err := yaml.NewDecoder(file).Decode(&config); err != nil {
-		log.Fatalf("Could not load config, %s", err.Error())
+		log.Fatalf("error loading config, %s", err.Error())
 	}
 	return config
 }
@@ -47,7 +47,7 @@ func LoadConfigFromFile(file *os.File) Config {
 func (config Config) UploadTimeoutDuration() time.Duration {
 	duration, err := time.ParseDuration(config.UploadTimeout)
 	if err != nil {
-		log.Fatalf("Could not load config: Could not parse upload timeout, %s", err.Error())
+		log.Fatalf("error loading config: error parsing upload timeout, %s", err.Error())
 	}
 	return duration
 }