changeset 51:07b6887d3557

pasterd: split into individual pages While here add a similar mini.css theme as in imgup.
author David Demelier <markand@malikania.fr>
date Mon, 21 Dec 2020 18:22:44 +0100
parents 520f57836ff3
children ef9f7f40abd1
files Makefile fragment-duration.c fragment-duration.h fragment-language.c fragment-language.h fragment-paste.c fragment-paste.h fragment.c fragment.h http.c page-download.c page-download.h page-fork.c page-fork.h page-index.c page-index.h page-new.c page-new.h page-paste.c page-paste.h page-search.c page-search.h page-static.c page-static.h page.c page.h paste.h pasterd.c themes/mini/fragments/duration.html themes/mini/fragments/footer.html themes/mini/fragments/header.html themes/mini/fragments/language.html themes/mini/fragments/paste.html themes/mini/pages/400.html themes/mini/pages/404.html themes/mini/pages/500.html themes/mini/pages/index.html themes/mini/pages/new.html themes/mini/pages/paste.html themes/mini/pages/search.html themes/mini/static/mini-default.min.css themes/minimal/400.html themes/minimal/404.html themes/minimal/500.html themes/minimal/footer.html themes/minimal/fragments/duration.html themes/minimal/fragments/footer.html themes/minimal/fragments/header.html themes/minimal/fragments/language.html themes/minimal/fragments/paste.html themes/minimal/header.html themes/minimal/index-paste.html themes/minimal/index.html themes/minimal/new.html themes/minimal/pages/400.html themes/minimal/pages/404.html themes/minimal/pages/500.html themes/minimal/pages/index.html themes/minimal/pages/new.html themes/minimal/pages/paste.html themes/minimal/pages/search.html themes/minimal/paste.html themes/minimal/search.html themes/siimple/400.html themes/siimple/404.html themes/siimple/500.html themes/siimple/footer.html themes/siimple/fragments/duration.html themes/siimple/fragments/footer.html themes/siimple/fragments/header.html themes/siimple/fragments/language.html themes/siimple/fragments/paste.html themes/siimple/header.html themes/siimple/index-paste.html themes/siimple/index.html themes/siimple/new.html themes/siimple/pages/400.html themes/siimple/pages/404.html themes/siimple/pages/500.html themes/siimple/pages/index.html themes/siimple/pages/new.html themes/siimple/pages/paste.html themes/siimple/pages/search.html themes/siimple/paste.html themes/siimple/search.html util.c util.h
diffstat 87 files changed, 2206 insertions(+), 1174 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Apr 27 12:08:16 2020 +0200
+++ b/Makefile	Mon Dec 21 18:22:44 2020 +0100
@@ -31,8 +31,25 @@
 
 VERSION=        0.2.1
 
-CORE_SRCS=      config.c database.c http.c log.c paste.c util.c
-CORE_HDRS=      config.h database.h http.h log.h paste.h util.h
+CORE_SRCS=      config.c                \
+                database.c              \
+                http.c                  \
+                log.c                   \
+                fragment-duration.c     \
+                fragment-language.c     \
+                fragment-paste.c        \
+                fragment.c              \
+                page-download.c         \
+                page-fork.c             \
+                page-index.c            \
+                page-new.c              \
+                page-paste.c            \
+                page-search.c           \
+                page-static.c           \
+                page.c                  \
+                paste.c                 \
+                util.c
+CORE_HDRS=      ${CORE_SRCS:.c=.h}
 CORE_OBJS=      ${CORE_SRCS:.c=.o}
 CORE_DEPS=      ${CORE_SRCS:.c=.d}
 CORE_LIB=       libpaster.a
@@ -52,7 +69,7 @@
                 -D_XOPEN_SOURCE=700 \
                 -DSHAREDIR=\"${SHAREDIR}\" \
                 -DVARDIR=\"${VARDIR}\"
-MY_LDFLAGS=     -static -lkcgi -lkcgihtml -lz
+MY_LDFLAGS=     -lkcgi -lkcgihtml -lz
 
 .SUFFIXES:
 .SUFFIXES: .o .c .in
@@ -113,7 +130,7 @@
 
 install: install-pasterd install-paster
 
-dist: clean
+dist:
 	mkdir -p paster-${VERSION}
 	cp -R extern paster-${VERSION}
 	cp -R themes paster-${VERSION}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment-duration.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,73 @@
+/*
+ * fragment-duration.c -- duration renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include <kcgi.h>
+
+#include "fragment-duration.h"
+#include "fragment.h"
+#include "util.h"
+
+struct template {
+	struct kreq *req;
+	const char *value;
+};
+
+static const char *keywords[] = {
+	"duration"
+};
+
+static int
+template(size_t keyword, void *arg)
+{
+	struct template *t = arg;
+
+	switch (keyword) {
+	case 0:
+		khttp_puts(t->req, t->value);
+		break;
+	default:
+		break;
+	}
+
+	return 1;
+}
+
+void
+fragment_duration(struct kreq *r, const char *duration)
+{
+	assert(r);
+	assert(duration);
+
+	struct template data = {
+		.req = r,
+		.value = duration
+	};
+	struct ktemplate kt = {
+		.key = keywords,
+		.keysz = NELEM(keywords),
+		.cb = template,
+		.arg = &data
+	};
+
+	fragment(r, &kt, "fragments/duration.html");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment-duration.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,27 @@
+/*
+ * fragment-duration.h -- duration renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_FRAGMENT_DURATION_H
+#define PASTER_FRAGMENT_DURATION_H
+
+struct kreq;
+
+void
+fragment_duration(struct kreq *, const char *);
+
+#endif /* !PASTER_FRAGMENT_DURATION_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment-language.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,80 @@
+/*
+ * fragment-language.c -- language renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include <kcgi.h>
+
+#include "fragment-language.h"
+#include "fragment.h"
+#include "util.h"
+
+struct template {
+	struct kreq *req;
+	const char *value;
+	bool selected;
+};
+
+static const char *keywords[] = {
+	"language",
+	"selected"
+};
+
+static int
+template(size_t keyword, void *arg)
+{
+	struct template *t = arg;
+
+	switch (keyword) {
+	case 0:
+		khttp_puts(t->req, t->value);
+		break;
+	case 1:
+		if (t->selected)
+			khttp_puts(t->req, "selected");
+		break;
+	default:
+		break;
+	}
+
+	return 1;
+}
+
+void
+fragment_language(struct kreq *r, const char *language, bool selected)
+{
+	assert(r);
+	assert(language);
+
+	struct template data = {
+		.req = r,
+		.value = language,
+		.selected = selected
+	};
+	struct ktemplate kt = {
+		.key = keywords,
+		.keysz = NELEM(keywords),
+		.cb = template,
+		.arg = &data
+	};
+
+	fragment(r, &kt, "fragments/language.html");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment-language.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,29 @@
+/*
+ * fragment-language.h -- language renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_FRAGMENT_LANGUAGE_H
+#define PASTER_FRAGMENT_LANGUAGE_H
+
+#include <stdbool.h>
+
+struct kreq;
+
+void
+fragment_language(struct kreq *, const char *, bool);
+
+#endif /* !PASTER_FRAGMENT_LANGUAGE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment-paste.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,101 @@
+/*
+ * fragment-paste.c -- paste index renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <time.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+#include "fragment-paste.h"
+#include "fragment.h"
+#include "paste.h"
+#include "util.h"
+
+struct template {
+	struct kreq *req;
+	const struct paste *paste;
+};
+
+static const char *keywords[] = {
+	"author",
+	"date",
+	"expiration",
+	"id",
+	"language",
+	"title"
+};
+
+static int
+template(size_t keyword, void *arg)
+{
+	struct template *tp = arg;
+	struct khtmlreq html;
+
+	khtml_open(&html, tp->req, KHTML_PRETTY);
+
+	switch (keyword) {
+	case 0:
+		khtml_puts(&html, tp->paste->author);
+		break;
+	case 1:
+		khtml_puts(&html, bstrftime("%c", localtime(&tp->paste->timestamp)));
+		break;
+	case 2:
+		khtml_puts(&html, ttl(tp->paste->timestamp, tp->paste->duration));
+		break;
+	case 3:
+		khtml_puts(&html, tp->paste->id);
+		break;
+	case 4:
+		khtml_puts(&html, tp->paste->language);
+		break;
+	case 5:
+		khtml_puts(&html, tp->paste->title);
+		break;
+	default:
+		break;
+	}
+
+	khtml_close(&html);
+
+	return 1;
+}
+
+void
+fragment_paste(struct kreq *r, const struct paste *paste)
+{
+	assert(r);
+	assert(paste);
+
+	struct template data = {
+		.req = r,
+		.paste = paste,
+	};
+	struct ktemplate kt = {
+		.key = keywords,
+		.keysz = NELEM(keywords),
+		.cb = template,
+		.arg = &data
+	};
+
+	fragment(r, &kt, "fragments/paste.html");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment-paste.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,28 @@
+/*
+ * fragment-paste.h -- paste index renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_FRAGMENT_PASTE_H
+#define PASTER_FRAGMENT_PASTE_H
+
+struct kreq;
+struct paste;
+
+void
+fragment_paste(struct kreq *, const struct paste *);
+
+#endif /* !PASTER_FRAGMENT_PASTE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,37 @@
+/*
+ * fragment.c -- fragment renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include <kcgi.h>
+
+#include "fragment.h"
+#include "util.h"
+
+void
+fragment(struct kreq *r, const struct ktemplate *t, const char *file)
+{
+	assert(r);
+	assert(t);
+	assert(file);
+
+	khttp_template(r, t, path(file));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fragment.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,28 @@
+/*
+ * fragment.h -- fragment renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_FRAGMENT_H
+#define PASTER_FRAGMENT_H
+
+struct kreq;
+struct ktemplate;
+
+void
+fragment(struct kreq *, const struct ktemplate *, const char *);
+
+#endif /* !PASTER_FRAGMENT_H */
--- a/http.c	Mon Apr 27 12:08:16 2020 +0200
+++ b/http.c	Mon Dec 21 18:22:44 2020 +0100
@@ -17,34 +17,24 @@
  */
 
 #include <sys/types.h>
-#include <sys/stat.h>
 #include <assert.h>
-#include <ctype.h>
-#include <limits.h>
 #include <stdarg.h>
 #include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
 
 #include <kcgi.h>
-#include <kcgihtml.h>
 
-#include "config.h"
 #include "database.h"
 #include "http.h"
 #include "log.h"
-#include "paste.h"
-#include "util.h"
 
-static void page_index(struct kreq *);
-static void page_new(struct kreq *);
-static void page_fork(struct kreq *);
-static void page_paste(struct kreq *);
-static void page_download(struct kreq *);
-static void page_search(struct kreq *);
-static void page_static(struct kreq *);
+#include "page-download.h"
+#include "page-fork.h"
+#include "page-index.h"
+#include "page-new.h"
+#include "page-paste.h"
+#include "page-search.h"
+#include "page-static.h"
+#include "page.h"
 
 enum page {
 	PAGE_INDEX,
@@ -77,837 +67,6 @@
 	[PAGE_STATIC]   = page_static
 };
 
-struct tmpl_index {
-	struct kreq *req;
-	struct paste pastes[10];
-	size_t count;
-	size_t current;
-};
-
-struct tmpl_paste {
-	struct kreq *req;
-	struct paste paste;
-};
-
-static const char *tmpl_index_keywords[] = {
-	"pastes"
-};
-
-static const char *tmpl_index_pastes_keywords[] = {
-	"id",
-	"name",
-	"author",
-	"language",
-	"expiration",
-	"date"
-};
-
-static const char *tmpl_paste_keywords[] = {
-	"id",
-	"title",
-	"author",
-	"language",
-	"code",
-	"timestamp",
-	"visible",
-	"duration"
-};
-
-static const char *tmpl_new_keywords[] = {
-	"title",        /* /fork only */
-	"author",       /* /fork only */
-	"code",         /* /fork only */
-	"private",      /* /fork only */
-	"languages",
-	"durations"
-};
-
-static const char *languages[] = {
-	"nohighlight",
-	"1c",
-	"abnf",
-	"accesslog",
-	"actionscript",
-	"ada",
-	"apache",
-	"applescript",
-	"arduino",
-	"armasm",
-	"asciidoc",
-	"aspectj",
-	"autohotkey",
-	"autoit",
-	"avrasm",
-	"awk",
-	"axapta",
-	"bash",
-	"basic",
-	"bnf",
-	"brainfuck",
-	"cal",
-	"capnproto",
-	"ceylon",
-	"clean",
-	"clojure",
-	"clojure-repl",
-	"cmake",
-	"coffeescript",
-	"coq",
-	"cos",
-	"cpp",
-	"crmsh",
-	"crystal",
-	"cs",
-	"csp",
-	"css",
-	"dart",
-	"delphi",
-	"diff",
-	"django",
-	"d",
-	"dns",
-	"dockerfile",
-	"dos",
-	"dsconfig",
-	"dts",
-	"dust",
-	"ebnf",
-	"elixir",
-	"elm",
-	"erb",
-	"erlang",
-	"erlang-repl",
-	"excel",
-	"fix",
-	"flix",
-	"fortran",
-	"fsharp",
-	"gams",
-	"gauss",
-	"gcode",
-	"gherkin",
-	"glsl",
-	"go",
-	"golo",
-	"gradle",
-	"groovy",
-	"haml",
-	"handlebars",
-	"haskell",
-	"haxe",
-	"hsp",
-	"htmlbars",
-	"http",
-	"hy",
-	"inform7",
-	"ini",
-	"irpf90",
-	"java",
-	"javascript",
-	"jboss-cli",
-	"json",
-	"julia",
-	"julia-repl",
-	"kotlin",
-	"lasso",
-	"ldif",
-	"leaf",
-	"less",
-	"lisp",
-	"livecodeserver",
-	"livescript",
-	"llvm",
-	"lsl",
-	"lua",
-	"makefile",
-	"markdown",
-	"mathematica",
-	"matlab",
-	"maxima",
-	"mel",
-	"mercury",
-	"mipsasm",
-	"mizar",
-	"mojolicious",
-	"monkey",
-	"moonscript",
-	"n1ql",
-	"nginx",
-	"nimrod",
-	"nix",
-	"nsis",
-	"objectivec",
-	"ocaml",
-	"openscad",
-	"oxygene",
-	"parser3",
-	"perl",
-	"pf",
-	"php",
-	"pony",
-	"powershell",
-	"processing",
-	"profile",
-	"prolog",
-	"protobuf",
-	"puppet",
-	"purebasic",
-	"python",
-	"q",
-	"qml",
-	"rib",
-	"r",
-	"roboconf",
-	"routeros",
-	"rsl",
-	"ruby",
-	"ruleslanguage",
-	"rust",
-	"scala",
-	"scheme",
-	"scilab",
-	"scss",
-	"shell",
-	"smali",
-	"smalltalk",
-	"sml",
-	"sqf",
-	"sql",
-	"stan",
-	"stata",
-	"step21",
-	"stylus",
-	"subunit",
-	"swift",
-	"taggerscript",
-	"tap",
-	"tcl",
-	"tex",
-	"thrift",
-	"tp",
-	"twig",
-	"typescript",
-	"vala",
-	"vbnet",
-	"vbscript-html",
-	"vbscript",
-	"verilog",
-	"vhdl",
-	"vim",
-	"x86asm",
-	"xl",
-	"xml",
-	"xquery",
-	"yaml",
-	"zephir",
-	NULL
-};
-
-static const struct {
-	const char *title;
-	long long int secs;
-} durations[] = {
-	{ "month",      PASTE_MONTH     },
-	{ "week",       PASTE_WEEK      },
-	{ "day",        PASTE_DAY       },
-	{ "hour",       PASTE_HOUR      },
-	{ NULL,         -1              }
-};
-
-static const char *
-template(const char *filename)
-{
-	/* Build path to the template file. */
-	static char path[PATH_MAX];
-
-	snprintf(path, sizeof (path), "%s/%s", config.themedir, filename);
-
-	return path;
-}
-
-static long long int
-duration(const char *val)
-{
-	if (strcmp(val, "hour") == 0)
-		return PASTE_HOUR;
-	if (strcmp(val, "day") == 0)
-		return PASTE_DAY;
-	if (strcmp(val, "week") == 0)
-		return PASTE_WEEK;
-	if (strcmp(val, "month") == 0)
-		return PASTE_MONTH;
-
-	/* Default to month. */
-	return PASTE_MONTH;
-}
-
-static const char *
-ttl(time_t timestamp, long long int duration)
-{
-	const time_t now = time(NULL);
-	const long long int left = duration - difftime(now, timestamp);
-
-	if (left < PASTE_HOUR)
-		return bprintf("%lld minute(s)", left / 60);
-	if (left < PASTE_DAY)
-		return bprintf("%lld hour(s)", left / 3600);
-
-	/* Other in days. */
-	return bprintf("%lld day(s)", left / 86400);
-}
-
-static void
-replace(char **dst, const char *s)
-{
-	assert(dst);
-	assert(s);
-
-	/* Trim leading spaces. */
-	while (*s && isspace(*s))
-		s++;
-
-	if (*s) {
-		free(*dst);
-		*dst = estrdup(s);
-	}
-}
-
-static void
-render_languages(struct kreq *req, const struct paste *paste)
-{
-	for (const char **l = languages; *l != NULL; ++l) {
-		const char *line;
-
-		if (paste->language && strcmp(paste->language, *l) == 0)
-			line = bprintf("<option value=\"%s\" selected>%s</option>", *l, *l);
-		else
-			line = bprintf("<option value=\"%s\">%s</option>", *l, *l);
-
-		khttp_puts(req, line);
-	}
-}
-
-static void
-render_durations(struct kreq *req, const struct paste *paste)
-{
-	for (size_t i = 0; durations[i].title != NULL; ++i) {
-		const char *line;
-
-		if (paste->duration == durations[i].secs)
-			line = bprintf("<option value=\"%s\" selected>%s</option>",
-			    durations[i].title, durations[i].title);
-		else
-			line = bprintf("<option value=\"%s\">%s</option>",
-			    durations[i].title, durations[i].title);
-
-		khttp_puts(req, line);
-	}
-}
-
-static int
-tmpl_paste(size_t index, void *arg)
-{
-	struct tmpl_paste *data = arg;
-	struct paste *paste = &data->paste;
-	struct khtmlreq htmlreq;
-
-	khtml_open(&htmlreq, data->req, KHTML_PRETTY);
-
-	switch (index) {
-	case 0:
-		khtml_puts(&htmlreq, paste->id);
-		break;
-	case 1:
-		khtml_puts(&htmlreq, paste->title);
-		break;
-	case 2:
-		khtml_puts(&htmlreq, paste->author);
-		break;
-	case 3:
-		khtml_puts(&htmlreq, paste->language);
-		break;
-	case 4:
-		khtml_puts(&htmlreq, paste->code);
-		break;
-	case 5:
-		khtml_puts(&htmlreq, bstrftime("%c", localtime(&paste->timestamp)));
-		break;
-	case 6:
-		khtml_puts(&htmlreq, bprintf("%s", paste->visible ? "Yes" : "No"));
-		break;
-	case 7:
-		khtml_puts(&htmlreq, ttl(paste->timestamp, paste->duration));
-		break;
-	default:
-		break;
-	}
-
-	khtml_close(&htmlreq);
-
-	return true;
-}
-
-static int
-tmpl_index_pastes(size_t index, void *arg)
-{
-	struct tmpl_index *data = arg;
-	struct paste *paste = &data->pastes[data->current];
-	struct khtmlreq htmlreq;
-
-	khtml_open(&htmlreq, data->req, KHTML_PRETTY);
-
-	switch (index) {
-	case 0:
-		khtml_puts(&htmlreq, paste->id);
-		break;
-	case 1:
-		khtml_puts(&htmlreq, paste->title);
-		break;
-	case 2:
-		khtml_puts(&htmlreq, paste->author);
-		break;
-	case 3:
-		khtml_puts(&htmlreq, paste->language);
-		break;
-	case 4:
-		khtml_puts(&htmlreq, ttl(paste->timestamp, paste->duration));
-		break;
-	case 5:
-		khtml_puts(&htmlreq, bstrftime("%c", localtime(&paste->timestamp)));
-		break;
-	default:
-		break;
-	}
-
-	khtml_close(&htmlreq);
-
-	return true;
-}
-
-static int
-tmpl_index(size_t index, void *arg)
-{
-	/* No check, only one index. */
-	(void)index;
-
-	struct tmpl_index *data = arg;
-	const struct ktemplate kt = {
-		.key    = tmpl_index_pastes_keywords,
-		.keysz  = 6,
-		.arg    = data,
-		.cb     = tmpl_index_pastes
-	};
-
-	for (size_t i = 0; i < data->count; ++i) {
-		khttp_template(data->req, &kt, template("index-paste.html"));
-		data->current++;
-	}
-
-	return true;
-}
-
-static int
-tmpl_new(size_t index, void *arg)
-{
-	struct tmpl_paste *data = arg;
-	struct paste *paste = &data->paste;
-	struct khtmlreq htmlreq;
-
-	khtml_open(&htmlreq, data->req, KHTML_PRETTY);
-
-	switch (index) {
-	case 0:
-		if (paste->title)
-			khtml_puts(&htmlreq, paste->title);
-		break;
-	case 1:
-		if (paste->author)
-			khtml_puts(&htmlreq, paste->author);
-		break;
-	case 2:
-		if (paste->code)
-			khtml_puts(&htmlreq, paste->code);
-		break;
-	case 3:
-		/* Add checked attribute to combobox. */
-		if (!paste->visible)
-			khttp_puts(data->req, "checked");
-		break;
-	case 4:
-		render_languages(data->req, paste);
-		break;
-	case 5:
-		render_durations(data->req, paste);
-		break;
-	default:
-		break;
-	};
-
-	khtml_close(&htmlreq);
-
-	return true;
-}
-
-static void
-page_header(struct kreq *req)
-{
-	khttp_template(req, NULL, template("header.html"));
-}
-
-static void
-page_footer(struct kreq *req)
-{
-	khttp_template(req, NULL, template("footer.html"));
-}
-
-static void
-page(struct kreq *req, const struct ktemplate *tmpl, enum khttp status, const char *file)
-{
-	khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML]);
-	khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]);
-	khttp_body(req);
-	page_header(req);
-	khttp_template(req, tmpl, template(file));
-	page_footer(req);
-	khttp_free(req);
-}
-
-static void
-page_index_get(struct kreq *req)
-{
-	struct tmpl_index data = {
-		.req    = req,
-		.count  = 10
-	};
-
-	if (!database_recents(data.pastes, &data.count))
-		page(req, NULL, KHTTP_500, "500.html");
-	else {
-		struct ktemplate kt = {
-			.key    = tmpl_index_keywords,
-			.keysz  = 1,
-			.arg    = &data,
-			.cb     = tmpl_index
-		};
-
-		page(req, &kt, KHTTP_200, "index.html");
-	}
-
-	for (size_t i = 0; i < data.count; ++i)
-		paste_finish(&data.pastes[i]);
-}
-
-static void
-page_index(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_index_get(req);
-		break;
-	default:
-		page(req, NULL, KHTTP_400, "400.html");
-		break;
-	}
-}
-
-static void
-page_new_get(struct kreq *req)
-{
-	struct tmpl_paste data = {
-		.req    = req
-	};
-	const struct ktemplate kt = {
-		.key    = tmpl_new_keywords,
-		.keysz  = 6,
-		.cb     = tmpl_new,
-		.arg    = &data
-	};
-
-	page(req, &kt, KHTTP_200, "new.html");
-}
-
-static void
-page_new_post(struct kreq *req)
-{
-	struct paste paste = {
-		.author         = estrdup("Anonymous"),
-		.title          = estrdup("Untitled"),
-		.language       = estrdup("nohighlight"),
-		.visible        = true
-	};
-
-	for (size_t i = 0; i < req->fieldsz; ++i) {
-		const char *key = req->fields[i].key;
-		const char *val = req->fields[i].val;
-
-		if (strcmp(key, "title") == 0)
-			replace(&paste.title, val);
-		else if (strcmp(key, "author") == 0)
-			replace(&paste.author, val);
-		else if (strcmp(key, "language") == 0)
-			replace(&paste.language, val);
-		else if (strcmp(key, "duration") == 0)
-			paste.duration = duration(val);
-		else if (strcmp(key, "code") == 0)
-			paste.code = estrdup(val);
-		else if (strcmp(key, "private") == 0)
-			paste.visible = strcmp(val, "on") != 0;
-	}
-
-	/* Add empty string if needed. */
-	if (!paste.code)
-		paste.code = estrdup("");
-
-	if (!database_insert(&paste))
-		page(req, NULL, KHTTP_500, "500.html");
-	else {
-		/* Redirect to paste details. */
-		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_302]);
-		khttp_head(req, kresps[KRESP_LOCATION], "/paste/%s", paste.id);
-		khttp_body(req);
-		khttp_free(req);
-	}
-
-	paste_finish(&paste);
-}
-
-static void
-page_new(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_new_get(req);
-		break;
-	case KMETHOD_POST:
-		page_new_post(req);
-		break;
-	default:
-		break;
-	}
-}
-
-static void
-page_fork_get(struct kreq *req)
-{
-	struct tmpl_paste data = {
-		.req = req
-	};
-
-	if (!database_get(&data.paste, req->path))
-		page(req, NULL, KHTTP_404, "404.html");
-	else {
-		const struct ktemplate kt = {
-			.key    = tmpl_new_keywords,
-			.keysz  = 6,
-			.cb     = tmpl_new,
-			.arg    = &data
-		};
-
-		page(req, &kt, KHTTP_200, "new.html");
-		paste_finish(&data.paste);
-	}
-}
-
-static void
-page_fork(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_fork_get(req);
-		break;
-	default:
-		page(req, NULL, KHTTP_400, "400.html");
-		break;
-	}
-}
-
-static void
-page_paste_get(struct kreq *req)
-{
-	struct tmpl_paste data = {
-		.req = req
-	};
-
-	if (!database_get(&data.paste, req->path))
-		page(req, NULL, KHTTP_404, "404.html");
-	else {
-		const struct ktemplate kt = {
-			.key    = tmpl_paste_keywords,
-			.keysz  = 8,
-			.cb     = tmpl_paste,
-			.arg    = &data
-		};
-
-		page(req, &kt, KHTTP_200, "paste.html");
-		paste_finish(&data.paste);
-	}
-}
-
-static void
-page_paste(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_paste_get(req);
-		break;
-	default:
-		page(req, NULL, KHTTP_400, "400.html");
-		break;
-	}
-}
-
-static void
-page_download_get(struct kreq *req)
-{
-	struct paste paste;
-
-	if (!database_get(&paste, req->path))
-		page(req, NULL, KHTTP_404, "404.html");
-	else {
-		khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]);
-#if 0
-		/* TODO: this seems to generated truncated files. */
-		khttp_head(req, kresps[KRESP_CONTENT_LENGTH], "%zu", strlen(paste.code));
-#endif
-		khttp_head(req, kresps[KRESP_CONNECTION], "keep-alive");
-		khttp_head(req, kresps[KRESP_CONTENT_DISPOSITION],
-		    "attachment; filename=\"%s.%s\"", paste.id, paste.language);
-		khttp_body(req);
-		khttp_puts(req, paste.code);
-		khttp_free(req);
-		paste_finish(&paste);
-	}
-}
-
-static void
-page_download(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_download_get(req);
-		break;
-	default:
-		page(req, NULL, KHTTP_400, "400.html");
-		break;
-	}
-}
-
-static void
-page_search_get(struct kreq *req)
-{
-	/* We re-use the /new form with an empty paste. */
-	struct tmpl_paste data = {
-		.req = req
-	};
-	const struct ktemplate kt = {
-		.key    = tmpl_new_keywords,
-		.keysz  = 6,
-		.cb     = tmpl_new,
-		.arg    = &data
-	};
-
-	page(req, &kt, KHTTP_200, "search.html");
-}
-
-static void
-page_search_post(struct kreq *req)
-{
-	struct tmpl_index data = {
-		.req    = req,
-		.count  = 10
-	};
-
-	const char *title = NULL;
-	const char *author = NULL;
-	const char *language = NULL;
-
-	for (size_t i = 0; i < req->fieldsz; ++i) {
-		const char *key = req->fields[i].key;
-		const char *val = req->fields[i].val;
-
-		if (strcmp(key, "title") == 0)
-			title = val;
-		else if (strcmp(key, "author") == 0)
-			author = val;
-		else if (strcmp(key, "language") == 0)
-			language = val;
-	}
-
-	/* Sets to null if they are empty. */
-	if (title && strlen(title) == 0)
-		title = NULL;
-	if (author && strlen(author) == 0)
-		author = NULL;
-	if (language && strlen(language) == 0)
-		language = NULL;
-
-	if (!database_search(data.pastes, &data.count, title, author, language))
-		page(req, NULL, KHTTP_500, "500.html");
-	else {
-		struct ktemplate kt = {
-			.key    = tmpl_index_keywords,
-			.keysz  = 1,
-			.arg    = &data,
-			.cb     = tmpl_index
-		};
-
-		page(req, &kt, KHTTP_200, "index.html");
-	}
-
-	for (size_t i = 0; i < data.count; ++i)
-		paste_finish(&data.pastes[i]);
-}
-
-static void
-page_search(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_search_get(req);
-		break;
-	case KMETHOD_POST:
-		page_search_post(req);
-		break;
-	default:
-		page(req, NULL, KHTTP_400, "400.html");
-		break;
-	}
-}
-
-static void
-page_static_get(struct kreq *req)
-{
-	struct stat st;
-	char path[PATH_MAX];
-
-	snprintf(path, sizeof (path), "%s%s", config.themedir, req->fullpath);
-
-	if (stat(path, &st) < 0)
-		page(req, NULL, KHTTP_404, "404.html");
-	else {
-		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]);
-		khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[req->mime]);
-		khttp_head(req, kresps[KRESP_CONTENT_LENGTH],
-		    "%llu", (unsigned long long)(st.st_size));
-		khttp_body(req);
-		khttp_template(req, NULL, path);
-		khttp_free(req);
-	}
-}
-
-static void
-page_static(struct kreq *req)
-{
-	switch (req->method) {
-	case KMETHOD_GET:
-		page_static_get(req);
-		break;
-	default:
-		page(req, NULL, KHTTP_400, "400.html");
-		break;
-	}
-}
-
 static void
 process(struct kreq *req)
 {
@@ -916,7 +75,7 @@
 	log_debug("http: accessing page '%s'", req->path);
 
 	if (req->page == PAGE_LAST)
-		page(req, NULL, KHTTP_404, "404.html");
+		page(req, NULL, KHTTP_404, "pages/404.html");
 	else
 		handlers[req->page](req);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-download.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,64 @@
+/*
+ * page-download.c -- page /download/<id>
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include <kcgi.h>
+
+#include "database.h"
+#include "page.h"
+#include "paste.h"
+
+static void
+get(struct kreq *r)
+{
+	struct paste paste = {0};
+
+	if (!database_get(&paste, r->path))
+		page(r, NULL, KHTTP_404, "404.html");
+	else {
+		khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_OCTET_STREAM]);
+#if 0
+		/* TODO: this seems to generated truncated files. */
+		khttp_head(r, kresps[KRESP_CONTENT_LENGTH], "%zu", strlen(paste.code));
+#endif
+		khttp_head(r, kresps[KRESP_CONNECTION], "keep-alive");
+		khttp_head(r, kresps[KRESP_CONTENT_DISPOSITION],
+		    "attachment; filename=\"%s.%s\"", paste.id, paste.language);
+		khttp_body(r);
+		khttp_puts(r, paste.code);
+		khttp_free(r);
+		paste_finish(&paste);
+	}
+}
+
+void
+page_download(struct kreq *r)
+{
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	default:
+		page(r, NULL, KHTTP_400, "400.html");
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-download.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,27 @@
+/*
+ * page-download.h -- page /download/<id>
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_DOWNLOAD_H
+#define PASTER_PAGE_DOWNLOAD_H
+
+struct kreq;
+
+void
+page_download(struct kreq *);
+
+#endif /* !PASTER_PAGE_DOWNLOAD_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-fork.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,56 @@
+/*
+ * page-fork.c -- page /fork/<id>
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <kcgi.h>
+
+#include "database.h"
+#include "page-new.h"
+#include "page.h"
+#include "paste.h"
+
+static void
+get(struct kreq *req)
+{
+	struct paste paste = {0};
+
+	if (!database_get(&paste, req->path))
+		page(req, NULL, KHTTP_404, "404.html");
+	else {
+		page_new_render(req, &paste);
+		paste_finish(&paste);
+	}
+}
+
+void
+page_fork(struct kreq *req)
+{
+	switch (req->method) {
+	case KMETHOD_GET:
+		get(req);
+		break;
+	default:
+		page(req, NULL, KHTTP_400, "400.html");
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-fork.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,27 @@
+/*
+ * page-fork.h -- page /fork/<id>
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_FORK_H
+#define PASTER_PAGE_FORK_H
+
+struct kreq;
+
+void
+page_fork(struct kreq *);
+
+#endif /* !PASTER_PAGE_FORK_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-index.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,105 @@
+/*
+ * page-index.c -- page /
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include <kcgi.h>
+
+#include "database.h"
+#include "fragment-paste.h"
+#include "page-index.h"
+#include "page.h"
+#include "paste.h"
+#include "util.h"
+
+struct template {
+	struct kreq *req;
+	const struct paste *pastes;
+	size_t pastesz;
+};
+
+static const char *keywords[] = {
+	"pastes"
+};
+
+static int
+template(size_t keyword, void *arg)
+{
+	struct template *tp = arg;
+
+	switch (keyword) {
+	case 0:
+		for (size_t i = 0; i < tp->pastesz; ++i)
+			fragment_paste(tp->req, &tp->pastes[i]);
+		break;
+	default:
+		break;
+	}
+
+	return 1;
+}
+
+static void
+get(struct kreq *r)
+{
+	struct paste pastes[10] = {0};
+	size_t pastesz = NELEM(pastes);
+
+	if (!database_recents(pastes, &pastesz))
+		page(r, NULL, KHTTP_500, "pages/500.html");
+	else
+		page_index_render(r, pastes, pastesz);
+
+	for (size_t i = 0; i < pastesz; ++i)
+		paste_finish(&pastes[i]);
+}
+
+void
+page_index_render(struct kreq *r, const struct paste *pastes, size_t pastesz)
+{
+	struct template data = {
+		.req = r,
+		.pastes = pastes,
+		.pastesz = pastesz
+	};
+
+	struct ktemplate kt = {
+		.key = keywords,
+		.keysz = NELEM(keywords),
+		.arg = &data,
+		.cb = template
+	};
+
+	page(r, &kt, KHTTP_200, "pages/index.html");
+}
+
+void
+page_index(struct kreq *r)
+{
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	default:
+		page(r, NULL, KHTTP_400, "400.html");
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-index.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,33 @@
+/*
+ * page-index.h -- page /
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_INDEX_H
+#define PASTER_PAGE_INDEX_H
+
+#include <stddef.h>
+
+struct kreq;
+struct paste;
+
+void
+page_index_render(struct kreq *, const struct paste *, size_t);
+
+void
+page_index(struct kreq *);
+
+#endif /* !PASTER_PAGE_INDEX_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-new.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,190 @@
+/*
+ * page-new.c -- page /new
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+#include "database.h"
+#include "fragment-duration.h"
+#include "fragment-language.h"
+#include "paste.h"
+#include "page-new.h"
+#include "page.h"
+#include "util.h"
+
+struct template {
+	struct kreq *req;
+	const struct paste *paste;
+};
+
+static const char *keywords[] = {
+	"code",
+	"durations",
+	"languages",
+	"title"
+};
+
+static const struct {
+	const char *title;
+	long long int secs;
+} durations[] = {
+	{ "day",        PASTE_DURATION_DAY      },
+	{ "hour",       PASTE_DURATION_HOUR     },
+	{ "week",       PASTE_DURATION_WEEK     },
+	{ "month",      PASTE_DURATION_MONTH    },
+};
+
+static bool
+is_selected(const struct paste *paste, const char *language)
+{
+	return paste && strcmp(paste->language, language) == 0;
+}
+
+static int
+template(size_t keyword, void *arg)
+{
+	struct template *tp = arg;
+	struct khtmlreq html;
+
+	khtml_open(&html, tp->req, KHTML_PRETTY);
+
+	switch (keyword) {
+	case 0:
+		if (tp->paste)
+			khtml_puts(&html, tp->paste->code);
+		break;
+	case 1:
+		for (size_t i = 0; i < NELEM(durations); ++i)
+			fragment_duration(tp->req, durations[i].title);
+		break;
+	case 2:
+		for (size_t i = 0; i < languagesz; ++i)
+			fragment_language(tp->req, languages[i],
+			    is_selected(tp->paste, languages[i]));
+		break;
+	case 3:
+		if (tp->paste)
+			khtml_puts(&html, tp->paste->title);
+		break;
+	default:
+		break;
+	}
+
+	khtml_close(&html);
+
+	return 1;
+}
+
+static long long int
+duration(const char *val)
+{
+	for (size_t i = 0; i < NELEM(durations); ++i)
+		if (strcmp(val, durations[i].title) == 0)
+			return durations[i].secs;
+
+	/* Default to month. */
+	return PASTE_DURATION_MONTH;
+}
+
+static void
+get(struct kreq *r)
+{
+	page_new_render(r, NULL);
+}
+
+static void
+post(struct kreq *r)
+{
+	struct paste paste = {
+		.author         = estrdup("Anonymous"),
+		.title          = estrdup("Untitled"),
+		.language       = estrdup("nohighlight"),
+		.code           = estrdup(""),
+		.visible        = true
+	};
+
+	for (size_t i = 0; i < r->fieldsz; ++i) {
+		const char *key = r->fields[i].key;
+		const char *val = r->fields[i].val;
+
+		if (strcmp(key, "title") == 0)
+			replace(&paste.title, val);
+		else if (strcmp(key, "author") == 0)
+			replace(&paste.author, val);
+		else if (strcmp(key, "language") == 0)
+			replace(&paste.language, val);
+		else if (strcmp(key, "duration") == 0)
+			paste.duration = duration(val);
+		else if (strcmp(key, "code") == 0)
+			paste.code = estrdup(val);
+		else if (strcmp(key, "private") == 0)
+			paste.visible = strcmp(val, "on") != 0;
+	}
+
+	if (!database_insert(&paste))
+		page(r, NULL, KHTTP_500, "500.html");
+	else {
+		/* Redirect to paste details. */
+		khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_302]);
+		khttp_head(r, kresps[KRESP_LOCATION], "/paste/%s", paste.id);
+		khttp_body(r);
+		khttp_free(r);
+	}
+
+	paste_finish(&paste);
+}
+
+void
+page_new_render(struct kreq *r, const struct paste *paste)
+{
+	struct template tp = {
+		.req = r,
+		.paste = paste
+	};
+	struct ktemplate kt = {
+		.key = keywords,
+		.keysz = NELEM(keywords),
+		.cb = template,
+		.arg = &tp
+	};
+
+	page(r, &kt, KHTTP_200, "pages/new.html");
+}
+
+void
+page_new(struct kreq *r)
+{
+	assert(r);
+
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	case KMETHOD_POST:
+		post(r);
+		break;
+	default:
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-new.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,31 @@
+/*
+ * page-new.h -- page /new
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_NEW_H
+#define PASTER_PAGE_NEW_H
+
+struct kreq;
+struct paste;
+
+void
+page_new_render(struct kreq *, const struct paste *);
+
+void
+page_new(struct kreq *);
+
+#endif /* !PASTER_PAGE_NEW_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-paste.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,129 @@
+/*
+ * page-paste.c -- page /paste/<id>
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+#include "database.h"
+#include "paste.h"
+#include "page-paste.h"
+#include "page.h"
+#include "util.h"
+
+struct template {
+	struct kreq *req;
+	struct paste *paste;
+};
+
+static const char *keywords[] = {
+	"author",
+	"code",
+	"date",
+	"expiration",
+	"id",
+	"language",
+	"title",
+	"visible"
+};
+
+static int
+template(size_t keyword, void *arg)
+{
+	const struct template *tp = arg;
+	struct khtmlreq html;
+
+	khtml_open(&html, tp->req, KHTML_PRETTY);
+
+	switch (keyword) {
+	case 0:
+		khtml_puts(&html, tp->paste->author);
+		break;
+	case 1:
+		khtml_puts(&html, tp->paste->code);
+		break;
+	case 2:
+		khtml_puts(&html, bstrftime("%c", localtime(&tp->paste->timestamp)));
+		break;
+	case 3:
+		khtml_puts(&html, ttl(tp->paste->timestamp, tp->paste->duration));
+		break;
+	case 4:
+		khtml_puts(&html, tp->paste->id);
+		break;
+	case 5:
+		khtml_puts(&html, tp->paste->language);
+		break;
+	case 6:
+		khtml_puts(&html, tp->paste->title);
+		break;
+	case 7:
+		khtml_puts(&html, bprintf(tp->paste->visible ? "Yes" : "No"));
+		break;
+	default:
+		break;
+	}
+
+	khtml_close(&html);
+
+	return 1;
+}
+
+static void
+get(struct kreq *r)
+{
+	struct paste paste = {0};
+	struct template data = {
+		.req = r,
+		.paste = &paste
+	};
+
+	if (!database_get(&paste, r->path))
+		page(r, NULL, KHTTP_404, "pages/404.html");
+	else {
+		const struct ktemplate kt = {
+			.key = keywords,
+			.keysz = NELEM(keywords),
+			.cb = template,
+			.arg = &data
+		};
+
+		page(r, &kt, KHTTP_200, "pages/paste.html");
+		paste_finish(&paste);
+	}
+}
+
+void
+page_paste(struct kreq *r)
+{
+	assert(r);
+
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	default:
+		page(r, NULL, KHTTP_400, "pages/400.html");
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-paste.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,27 @@
+/*
+ * page-paste.h -- page /paste/<id>
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_IMAGE_H
+#define PASTER_PAGE_IMAGE_H
+
+struct kreq;
+
+void
+page_paste(struct kreq *);
+
+#endif /* !PASTER_PAGE_IMAGE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-search.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,122 @@
+/*
+ * page-search.c -- page /search
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <kcgi.h>
+
+#include "database.h"
+#include "fragment-language.h"
+#include "page-index.h"
+#include "page-search.h"
+#include "page.h"
+#include "paste.h"
+#include "util.h"
+
+static const char *keywords[] = {
+	"languages"
+};
+
+static int
+template(size_t keyword, void *arg)
+{
+	struct kreq *r = arg;
+
+	switch (keyword) {
+	case 0:
+		for (size_t i = 0; i < languagesz; ++i)
+			fragment_language(r, languages[i], false);
+		break;
+	default:
+		break;
+	}
+
+	return 1;
+}
+
+static void
+get(struct kreq *r)
+{
+	struct ktemplate kt = {
+		.key = keywords,
+		.keysz = NELEM(keywords),
+		.cb = template,
+		.arg = r
+	};
+
+	page(r, &kt, KHTTP_200, "pages/search.html");
+}
+
+static void
+post(struct kreq *r)
+{
+	struct paste pastes[10] = {0};
+	size_t pastesz = NELEM(pastes);
+	const char *title = NULL;
+	const char *author = NULL;
+	const char *language = NULL;
+
+	for (size_t i = 0; i < r->fieldsz; ++i) {
+		const char *key = r->fields[i].key;
+		const char *val = r->fields[i].val;
+
+		if (strcmp(key, "title") == 0)
+			title = val;
+		else if (strcmp(key, "author") == 0)
+			author = val;
+		else if (strcmp(key, "language") == 0)
+			language = val;
+	}
+
+	/* Sets to null if they are empty. */
+	if (title && strlen(title) == 0)
+		title = NULL;
+	if (author && strlen(author) == 0)
+		author = NULL;
+
+	if (!database_search(pastes, &pastesz, title, author, language))
+		page(r, NULL, KHTTP_500, "pages/500.html");
+	else
+		page_index_render(r, pastes, pastesz);
+
+	for (size_t i = 0; i < pastesz; ++i)
+		paste_finish(&pastes[i]);
+}
+
+void
+page_search(struct kreq *r)
+{
+	assert(r);
+
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	case KMETHOD_POST:
+		post(r);
+		break;
+	default:
+		page(r, NULL, KHTTP_400, "pages/400.html");
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-search.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,27 @@
+/*
+ * page-search.h -- page /search
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_SEARCH_H
+#define PASTER_PAGE_SEARCH_H
+
+struct kreq;
+
+void
+page_search(struct kreq *);
+
+#endif /* !PASTER_PAGE_SEARCH_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-static.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,64 @@
+/*
+ * page-static.c -- page /static
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "config.h"
+#include "page.h"
+
+static void
+get(struct kreq *req)
+{
+	struct stat st;
+	char path[PATH_MAX];
+
+	snprintf(path, sizeof (path), "%s%s", config.themedir, req->fullpath);
+
+	if (stat(path, &st) < 0)
+		page(req, NULL, KHTTP_404, "pages/404.html");
+	else {
+		khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]);
+		khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[req->mime]);
+		khttp_head(req, kresps[KRESP_CONTENT_LENGTH],
+		    "%llu", (unsigned long long)(st.st_size));
+		khttp_body(req);
+		khttp_template(req, NULL, path);
+		khttp_free(req);
+	}
+}
+
+void
+page_static(struct kreq *r)
+{
+	assert(r);
+
+	switch (r->method) {
+	case KMETHOD_GET:
+		get(r);
+		break;
+	default:
+		page(r, NULL, KHTTP_400, "pages/400.html");
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page-static.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,27 @@
+/*
+ * page-static.h -- page /static
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_STATIC_H
+#define PASTER_PAGE_STATIC_H
+
+struct kreq;
+
+void
+page_static(struct kreq *);
+
+#endif /* !PASTER_PAGE_STATIC_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page.c	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,32 @@
+/*
+ * page.c -- page renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "page.h"
+#include "util.h"
+
+void
+page(struct kreq *req, const struct ktemplate *tmpl, enum khttp status, const char *file)
+{
+	khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML]);
+	khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]);
+	khttp_body(req);
+	khttp_template(req, NULL, path("fragments/header.html"));
+	khttp_template(req, tmpl, path(file));
+	khttp_template(req, NULL, path("fragments/footer.html"));
+	khttp_free(req);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/page.h	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,30 @@
+/*
+ * page.h -- page renderer
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PASTER_PAGE_H
+#define PASTER_PAGE_H
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <kcgi.h>
+
+void
+page(struct kreq *, const struct ktemplate *, enum khttp, const char *);
+
+#endif /* !PASTER_PAGE_H */
--- a/paste.h	Mon Apr 27 12:08:16 2020 +0200
+++ b/paste.h	Mon Dec 21 18:22:44 2020 +0100
@@ -22,16 +22,11 @@
 #include <stdbool.h>
 #include <time.h>
 
-#define PASTE_HOUR      3600          /*!< Seconds in one hour. */
-#define PASTE_DAY       86400         /*!< Seconds in one day. */
-#define PASTE_WEEK      604800        /*!< Seconds in one week. */
-#define PASTE_MONTH     2592000       /*!< Rounded to 30 days. */
+#define PASTE_DURATION_HOUR      3600           /* Seconds in one hour. */
+#define PASTE_DURATION_DAY       86400          /* Seconds in one day. */
+#define PASTE_DURATION_WEEK      604800         /* Seconds in one week. */
+#define PASTE_DURATION_MONTH     2592000        /* Rounded to 30 days. */
 
-/**
- * \brief Paste structure.
- *
- * Every string in the paste is assumed to be allocated on the heap.
- */
 struct paste {
 	char *id;
 	char *title;
@@ -40,7 +35,7 @@
 	char *code;
 	time_t timestamp;
 	bool visible;
-        long long int duration;
+	long long int duration;
 };
 
 void
--- a/pasterd.c	Mon Apr 27 12:08:16 2020 +0200
+++ b/pasterd.c	Mon Dec 21 18:22:44 2020 +0100
@@ -62,7 +62,7 @@
 }
  
 int
-main(int argc, char **argv)
+main(int argc, char **argv, char **env)
 {
 	const char *value;
 	int opt;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/fragments/duration.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<option value="@@duration@@">@@duration@@</option>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/fragments/footer.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,3 @@
+		</div>
+	<body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/fragments/header.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<link rel="stylesheet" href="/static/mini-default.min.css">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+		<style>
+		.container {
+			width: 80%;
+		}
+		.responsive-label {
+			align-items: center;
+		}
+		</style>
+	</head>
+	<body>
+		<header>
+			<a href="/" class="logo">paster</a>
+			<a href="/new" class="button">new</a>
+			<a href="/search" class="button">search</a>
+		</header>
+		<div class="container">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/fragments/language.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<option value="@@language@@" @@selected@@>@@language@@</option>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/fragments/paste.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,7 @@
+<tr>
+	<td><a href="/paste/@@id@@">@@title@@</a></td>
+	<td>@@author@@</td>
+	<td>@@language@@</td>
+	<td>@@date@@</td>
+	<td>@@expiration@@</td>
+</tr>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/400.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Bad request</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/404.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Not found</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/500.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Internal server error</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/index.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,17 @@
+	<h1>Recent pastes</h1>
+
+	<table>
+		<thead>
+			<tr>
+				<th>Name</th>
+				<th>Author</th>
+				<th>Language</th>
+				<th>Date</th>
+				<th>Expires in</th>
+			<tr>
+		</thead>
+	<tbody>
+
+	@@pastes@@
+
+	</tbody>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/new.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,53 @@
+	<h1>Create paste</h1>
+
+	<form action="/new" method="post" enctype="multipart/form-data">
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="title">Title</label>
+			</div>
+			<div class="col-md-10">
+				<input id="title" name="title" type="text" placeholder="Untitled" />
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="author">Author</label>
+			</div>
+			<div class="col-md-10">
+				<input id="author" name="author" type="text" placeholder="Anonymous" />
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="duration">Expires in</label>
+			</div>
+			<div class="col-md-10">
+				<select id="duration" name="duration">
+					@@durations@@
+				</select>
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="private">Private</label>
+			</div>
+			<div class="col-md-10">
+				<input id="private" type="checkbox" name="private" checked></input>
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-12">
+				<textarea id="code" placeholder="What do you want to share?" rows="10" name="code" style="width: 100%">@@code@@</textarea>
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<button class="primary">Submit</button>
+			</div>
+		</div>
+	</form>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/paste.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,71 @@
+	<h2>Actions</h2>
+	<div class="row responsive-label">
+		<div class="col-md-1">
+			<a class="button" href="/download/@@id@@">Fork</a>
+		</div>
+		<div class="col-md-1">
+			<a class="button" href="/download/@@id@@">Download</a>
+		</div>
+	</div>
+
+	<h2>Info</h2>
+
+	<div class="row responsive-label">
+		<div class="col-md-2">
+			<label for="title">Title</label>
+		</div>
+		<div class="col-md-10">
+			<label id="title" placeholder="Untitled">@@title@@</label>
+		</div>
+	</div>
+
+	<div class="row responsive-label">
+		<div class="col-md-2">
+			<label for="author">Author</label>
+		</div>
+		<div class="col-md-10">
+			<label id="author" placeholder="Anonymous">@@author@@</label>
+		</div>
+	</div>
+
+	<div class="row responsive-label">
+		<div class="col-md-2">
+			<label for="duration">Language</label>
+		</div>
+		<div class="col-md-10">
+			<label id="duration">@@language@@</label>
+		</div>
+	</div>
+
+	<div class="row responsive-label">
+		<div class="col-md-2">
+			<label for="duration">Date</label>
+		</div>
+		<div class="col-md-10">
+			<label id="duration">@@date@@</label>
+		</div>
+	</div>
+
+	<div class="row responsive-label">
+		<div class="col-md-2">
+			<label for="duration">Is visible?</label>
+		</div>
+		<div class="col-md-10">
+			<label id="duration">@@visible@@</label>
+		</div>
+	</div>
+
+	<div class="row responsive-label">
+		<div class="col-md-2">
+			<label for="duration">Lifetime?</label>
+		</div>
+		<div class="col-md-10">
+			<label id="duration">@@expiration@@</label>
+		</div>
+	</div>
+
+	<div class="row responsive-label">
+		<div class="col-md-12">
+			<pre><code class="@@language@@">@@code@@</code></pre>
+		</div>
+	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/pages/search.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,38 @@
+	<h1>Search pastes</h1>
+
+	<form action="/search" method="post">
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="title">Title</label>
+			</div>
+			<div class="col-md-10">
+				<input id="title" name="title" type="text" placeholder="Title" /></td>
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="author">Author</label>
+			</div>
+			<div class="col-md-10">
+				<input id="author" name="author" type="text" placeholder="Author" /></td>
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-2">
+				<label for="language">Language</label>
+			</div>
+			<div class="col-md-10">
+				<select name="language">
+					@@languages@@
+				</select>
+			</div>
+		</div>
+
+		<div class="row responsive-label">
+			<div class="col-md-12">
+				<button class="primary">Submit</button>
+			</div>
+		</div>
+	</form>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/mini/static/mini-default.min.css	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+:root{--fore-color:#111;--secondary-fore-color:#444;--back-color:#f8f8f8;--secondary-back-color:#f0f0f0;--blockquote-color:#f57c00;--pre-color:#1565c0;--border-color:#aaa;--secondary-border-color:#ddd;--heading-ratio:1.19;--universal-margin:.5rem;--universal-padding:.5rem;--universal-border-radius:.125rem;--a-link-color:#0277bd;--a-visited-color:#01579b}html{font-size:16px}a,b,del,em,i,ins,q,span,strong,u{font-size:1em}html,*{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", Helvetica, sans-serif;line-height:1.5;-webkit-text-size-adjust:100%}*{font-size:1rem}body{margin:0;color:var(--fore-color);background:var(--back-color)}details{display:block}summary{display:list-item}abbr[title]{border-bottom:none;text-decoration:underline dotted}input{overflow:visible}img{max-width:100%;height:auto}h1,h2,h3,h4,h5,h6{line-height:1.2;margin:calc(1.5 * var(--universal-margin)) var(--universal-margin);font-weight:500}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:var(--secondary-fore-color);display:block;margin-top:-.25rem}h1{font-size:calc(1rem * var(--heading-ratio) * var(--heading-ratio) * var(--heading-ratio) * var(--heading-ratio))}h2{font-size:calc(1rem * var(--heading-ratio) * var(--heading-ratio) * var(--heading-ratio))}h3{font-size:calc(1rem * var(--heading-ratio) * var(--heading-ratio))}h4{font-size:calc(1rem * var(--heading-ratio))}h5{font-size:1rem}h6{font-size:calc(1rem / var(--heading-ratio))}p{margin:var(--universal-margin)}ol,ul{margin:var(--universal-margin);padding-left:calc(2 * var(--universal-margin))}b,strong{font-weight:700}hr{box-sizing:content-box;border:0;line-height:1.25em;margin:var(--universal-margin);height:.0625rem;background:linear-gradient(to right, transparent, var(--border-color) 20%, var(--border-color) 80%, transparent)}blockquote{display:block;position:relative;font-style:italic;color:var(--secondary-fore-color);margin:var(--universal-margin);padding:calc(3 * var(--universal-padding));border:.0625rem solid var(--secondary-border-color);border-left:.375rem solid var(--blockquote-color);border-radius:0 var(--universal-border-radius) var(--universal-border-radius) 0}blockquote:before{position:absolute;top:calc(0rem - var(--universal-padding));left:0;font-family:sans-serif;font-size:3rem;font-weight:700;content:"\201c";color:var(--blockquote-color)}blockquote[cite]:after{font-style:normal;font-size:.75em;font-weight:700;content:"\a—  " attr(cite);white-space:pre}code,kbd,pre,samp{font-family:Menlo, Consolas, monospace;font-size:.85em}code{background:var(--secondary-back-color);border-radius:var(--universal-border-radius);padding:calc(var(--universal-padding) / 4) calc(var(--universal-padding) / 2)}kbd{background:var(--fore-color);color:var(--back-color);border-radius:var(--universal-border-radius);padding:calc(var(--universal-padding) / 4) calc(var(--universal-padding) / 2)}pre{overflow:auto;background:var(--secondary-back-color);padding:calc(1.5 * var(--universal-padding));margin:var(--universal-margin);border:.0625rem solid var(--secondary-border-color);border-left:.25rem solid var(--pre-color);border-radius:0 var(--universal-border-radius) var(--universal-border-radius) 0}sup,sub,code,kbd{line-height:0;position:relative;vertical-align:baseline}small,sup,sub,figcaption{font-size:.75em}sup{top:-.5em}sub{bottom:-.25em}figure{margin:var(--universal-margin)}figcaption{color:var(--secondary-fore-color)}a{text-decoration:none}a:link{color:var(--a-link-color)}a:visited{color:var(--a-visited-color)}a:hover,a:focus{text-decoration:underline}.container{margin:0 auto;padding:0 calc(1.5 * var(--universal-padding))}.row{box-sizing:border-box;display:flex;flex:0 1 auto;flex-flow:row wrap}.col-sm,[class^='col-sm-'],[class^='col-sm-offset-'],.row[class*='cols-sm-']>*{box-sizing:border-box;flex:0 0 auto;padding:0 calc(var(--universal-padding) / 2)}.col-sm,.row.cols-sm>*{max-width:100%;flex-grow:1;flex-basis:0}.col-sm-1,.row.cols-sm-1>*{max-width:8.33333%;flex-basis:8.33333%}.col-sm-offset-0{margin-left:0}.col-sm-2,.row.cols-sm-2>*{max-width:16.66667%;flex-basis:16.66667%}.col-sm-offset-1{margin-left:8.33333%}.col-sm-3,.row.cols-sm-3>*{max-width:25%;flex-basis:25%}.col-sm-offset-2{margin-left:16.66667%}.col-sm-4,.row.cols-sm-4>*{max-width:33.33333%;flex-basis:33.33333%}.col-sm-offset-3{margin-left:25%}.col-sm-5,.row.cols-sm-5>*{max-width:41.66667%;flex-basis:41.66667%}.col-sm-offset-4{margin-left:33.33333%}.col-sm-6,.row.cols-sm-6>*{max-width:50%;flex-basis:50%}.col-sm-offset-5{margin-left:41.66667%}.col-sm-7,.row.cols-sm-7>*{max-width:58.33333%;flex-basis:58.33333%}.col-sm-offset-6{margin-left:50%}.col-sm-8,.row.cols-sm-8>*{max-width:66.66667%;flex-basis:66.66667%}.col-sm-offset-7{margin-left:58.33333%}.col-sm-9,.row.cols-sm-9>*{max-width:75%;flex-basis:75%}.col-sm-offset-8{margin-left:66.66667%}.col-sm-10,.row.cols-sm-10>*{max-width:83.33333%;flex-basis:83.33333%}.col-sm-offset-9{margin-left:75%}.col-sm-11,.row.cols-sm-11>*{max-width:91.66667%;flex-basis:91.66667%}.col-sm-offset-10{margin-left:83.33333%}.col-sm-12,.row.cols-sm-12>*{max-width:100%;flex-basis:100%}.col-sm-offset-11{margin-left:91.66667%}.col-sm-normal{order:initial}.col-sm-first{order:-999}.col-sm-last{order:999}@media screen and (min-width: 768px){.col-md,[class^='col-md-'],[class^='col-md-offset-'],.row[class*='cols-md-']>*{box-sizing:border-box;flex:0 0 auto;padding:0 calc(var(--universal-padding) / 2)}.col-md,.row.cols-md>*{max-width:100%;flex-grow:1;flex-basis:0}.col-md-1,.row.cols-md-1>*{max-width:8.33333%;flex-basis:8.33333%}.col-md-offset-0{margin-left:0}.col-md-2,.row.cols-md-2>*{max-width:16.66667%;flex-basis:16.66667%}.col-md-offset-1{margin-left:8.33333%}.col-md-3,.row.cols-md-3>*{max-width:25%;flex-basis:25%}.col-md-offset-2{margin-left:16.66667%}.col-md-4,.row.cols-md-4>*{max-width:33.33333%;flex-basis:33.33333%}.col-md-offset-3{margin-left:25%}.col-md-5,.row.cols-md-5>*{max-width:41.66667%;flex-basis:41.66667%}.col-md-offset-4{margin-left:33.33333%}.col-md-6,.row.cols-md-6>*{max-width:50%;flex-basis:50%}.col-md-offset-5{margin-left:41.66667%}.col-md-7,.row.cols-md-7>*{max-width:58.33333%;flex-basis:58.33333%}.col-md-offset-6{margin-left:50%}.col-md-8,.row.cols-md-8>*{max-width:66.66667%;flex-basis:66.66667%}.col-md-offset-7{margin-left:58.33333%}.col-md-9,.row.cols-md-9>*{max-width:75%;flex-basis:75%}.col-md-offset-8{margin-left:66.66667%}.col-md-10,.row.cols-md-10>*{max-width:83.33333%;flex-basis:83.33333%}.col-md-offset-9{margin-left:75%}.col-md-11,.row.cols-md-11>*{max-width:91.66667%;flex-basis:91.66667%}.col-md-offset-10{margin-left:83.33333%}.col-md-12,.row.cols-md-12>*{max-width:100%;flex-basis:100%}.col-md-offset-11{margin-left:91.66667%}.col-md-normal{order:initial}.col-md-first{order:-999}.col-md-last{order:999}}@media screen and (min-width: 1280px){.col-lg,[class^='col-lg-'],[class^='col-lg-offset-'],.row[class*='cols-lg-']>*{box-sizing:border-box;flex:0 0 auto;padding:0 calc(var(--universal-padding) / 2)}.col-lg,.row.cols-lg>*{max-width:100%;flex-grow:1;flex-basis:0}.col-lg-1,.row.cols-lg-1>*{max-width:8.33333%;flex-basis:8.33333%}.col-lg-offset-0{margin-left:0}.col-lg-2,.row.cols-lg-2>*{max-width:16.66667%;flex-basis:16.66667%}.col-lg-offset-1{margin-left:8.33333%}.col-lg-3,.row.cols-lg-3>*{max-width:25%;flex-basis:25%}.col-lg-offset-2{margin-left:16.66667%}.col-lg-4,.row.cols-lg-4>*{max-width:33.33333%;flex-basis:33.33333%}.col-lg-offset-3{margin-left:25%}.col-lg-5,.row.cols-lg-5>*{max-width:41.66667%;flex-basis:41.66667%}.col-lg-offset-4{margin-left:33.33333%}.col-lg-6,.row.cols-lg-6>*{max-width:50%;flex-basis:50%}.col-lg-offset-5{margin-left:41.66667%}.col-lg-7,.row.cols-lg-7>*{max-width:58.33333%;flex-basis:58.33333%}.col-lg-offset-6{margin-left:50%}.col-lg-8,.row.cols-lg-8>*{max-width:66.66667%;flex-basis:66.66667%}.col-lg-offset-7{margin-left:58.33333%}.col-lg-9,.row.cols-lg-9>*{max-width:75%;flex-basis:75%}.col-lg-offset-8{margin-left:66.66667%}.col-lg-10,.row.cols-lg-10>*{max-width:83.33333%;flex-basis:83.33333%}.col-lg-offset-9{margin-left:75%}.col-lg-11,.row.cols-lg-11>*{max-width:91.66667%;flex-basis:91.66667%}.col-lg-offset-10{margin-left:83.33333%}.col-lg-12,.row.cols-lg-12>*{max-width:100%;flex-basis:100%}.col-lg-offset-11{margin-left:91.66667%}.col-lg-normal{order:initial}.col-lg-first{order:-999}.col-lg-last{order:999}}:root{--card-back-color:#f8f8f8;--card-fore-color:#111;--card-border-color:#ddd}.card{display:flex;flex-direction:column;justify-content:space-between;align-self:center;position:relative;width:100%;background:var(--card-back-color);color:var(--card-fore-color);border:.0625rem solid var(--card-border-color);border-radius:var(--universal-border-radius);margin:var(--universal-margin);overflow:hidden}@media screen and (min-width: 320px){.card{max-width:320px}}.card>.section{background:var(--card-back-color);color:var(--card-fore-color);box-sizing:border-box;margin:0;border:0;border-radius:0;border-bottom:.0625rem solid var(--card-border-color);padding:var(--universal-padding);width:100%}.card>.section.media{height:200px;padding:0;-o-object-fit:cover;object-fit:cover}.card>.section:last-child{border-bottom:0}@media screen and (min-width: 240px){.card.small{max-width:240px}}@media screen and (min-width: 480px){.card.large{max-width:480px}}.card.fluid{max-width:100%;width:auto}.card.warning{--card-back-color:#ffca28;--card-border-color:#e8b825}.card.error{--card-back-color:#b71c1c;--card-fore-color:#f8f8f8;--card-border-color:#a71a1a}.card>.section.dark{--card-back-color:#e0e0e0}.card>.section.double-padded{padding:calc(1.5 * var(--universal-padding))}:root{--form-back-color:#f0f0f0;--form-fore-color:#111;--form-border-color:#ddd;--input-back-color:#f8f8f8;--input-fore-color:#111;--input-border-color:#ddd;--input-focus-color:#0288d1;--input-invalid-color:#d32f2f;--button-back-color:#e2e2e2;--button-hover-back-color:#dcdcdc;--button-fore-color:#212121;--button-border-color:rgba(0,0,0,0);--button-hover-border-color:rgba(0,0,0,0);--button-group-border-color:rgba(124,124,124,0.54)}form{background:var(--form-back-color);color:var(--form-fore-color);border:.0625rem solid var(--form-border-color);border-radius:var(--universal-border-radius);margin:var(--universal-margin);padding:calc(2 * var(--universal-padding)) var(--universal-padding)}fieldset{border:.0625rem solid var(--form-border-color);border-radius:var(--universal-border-radius);margin:calc(var(--universal-margin) / 4);padding:var(--universal-padding)}legend{box-sizing:border-box;display:table;max-width:100%;white-space:normal;font-weight:700;padding:calc(var(--universal-padding) / 2)}label{padding:calc(var(--universal-padding) / 2) var(--universal-padding)}.input-group{display:inline-block}.input-group.fluid{display:flex;align-items:center;justify-content:center}.input-group.fluid>input{max-width:100%;flex-grow:1;flex-basis:0px}@media screen and (max-width: 767px){.input-group.fluid{align-items:stretch;flex-direction:column}}.input-group.vertical{display:flex;align-items:stretch;flex-direction:column}.input-group.vertical>input{max-width:100%;flex-grow:1;flex-basis:0px}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input:not([type]),[type="text"],[type="email"],[type="number"],[type="search"],[type="password"],[type="url"],[type="tel"],[type="checkbox"],[type="radio"],textarea,select{box-sizing:border-box;background:var(--input-back-color);color:var(--input-fore-color);border:.0625rem solid var(--input-border-color);border-radius:var(--universal-border-radius);margin:calc(var(--universal-margin) / 2);padding:var(--universal-padding) calc(1.5 * var(--universal-padding))}input:not([type="button"]):not([type="submit"]):not([type="reset"]):hover,input:not([type="button"]):not([type="submit"]):not([type="reset"]):focus,textarea:hover,textarea:focus,select:hover,select:focus{border-color:var(--input-focus-color);box-shadow:none}input:not([type="button"]):not([type="submit"]):not([type="reset"]):invalid,input:not([type="button"]):not([type="submit"]):not([type="reset"]):focus:invalid,textarea:invalid,textarea:focus:invalid,select:invalid,select:focus:invalid{border-color:var(--input-invalid-color);box-shadow:none}input:not([type="button"]):not([type="submit"]):not([type="reset"])[readonly],textarea[readonly],select[readonly]{background:var(--secondary-back-color)}select{max-width:100%}option{overflow:hidden;text-overflow:ellipsis}[type="checkbox"],[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;position:relative;height:calc(1rem + var(--universal-padding) / 2);width:calc(1rem + var(--universal-padding) / 2);vertical-align:text-bottom;padding:0;flex-basis:calc(1rem + var(--universal-padding) / 2) !important;flex-grow:0 !important}[type="checkbox"]:checked:before,[type="radio"]:checked:before{position:absolute}[type="checkbox"]:checked:before{content:'\2713';font-family:sans-serif;font-size:calc(1rem + var(--universal-padding) / 2);top:calc(0rem - var(--universal-padding));left:calc(var(--universal-padding) / 4)}[type="radio"]{border-radius:100%}[type="radio"]:checked:before{border-radius:100%;content:'';top:calc(.0625rem + var(--universal-padding) / 2);left:calc(.0625rem + var(--universal-padding) / 2);background:var(--input-fore-color);width:0.5rem;height:0.5rem}:placeholder-shown{color:var(--input-fore-color)}::-ms-placeholder{color:var(--input-fore-color);opacity:0.54}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,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button{overflow:visible;text-transform:none}button,[type="button"],[type="submit"],[type="reset"],a.button,label.button,.button,a[role="button"],label[role="button"],[role="button"]{display:inline-block;background:var(--button-back-color);color:var(--button-fore-color);border:.0625rem solid var(--button-border-color);border-radius:var(--universal-border-radius);padding:var(--universal-padding) calc(1.5 * var(--universal-padding));margin:var(--universal-margin);text-decoration:none;cursor:pointer;transition:background 0.3s}button:hover,button:focus,[type="button"]:hover,[type="button"]:focus,[type="submit"]:hover,[type="submit"]:focus,[type="reset"]:hover,[type="reset"]:focus,a.button:hover,a.button:focus,label.button:hover,label.button:focus,.button:hover,.button:focus,a[role="button"]:hover,a[role="button"]:focus,label[role="button"]:hover,label[role="button"]:focus,[role="button"]:hover,[role="button"]:focus{background:var(--button-hover-back-color);border-color:var(--button-hover-border-color)}input:disabled,input[disabled],textarea:disabled,textarea[disabled],select:disabled,select[disabled],button:disabled,button[disabled],.button:disabled,.button[disabled],[role="button"]:disabled,[role="button"][disabled]{cursor:not-allowed;opacity:.75}.button-group{display:flex;border:.0625rem solid var(--button-group-border-color);border-radius:var(--universal-border-radius);margin:var(--universal-margin)}.button-group>button,.button-group [type="button"],.button-group>[type="submit"],.button-group>[type="reset"],.button-group>.button,.button-group>[role="button"]{margin:0;max-width:100%;flex:1 1 auto;text-align:center;border:0;border-radius:0;box-shadow:none}.button-group>:not(:first-child){border-left:.0625rem solid var(--button-group-border-color)}@media screen and (max-width: 767px){.button-group{flex-direction:column}.button-group>:not(:first-child){border:0;border-top:.0625rem solid var(--button-group-border-color)}}button.primary,[type="button"].primary,[type="submit"].primary,[type="reset"].primary,.button.primary,[role="button"].primary{--button-back-color:#1976d2;--button-fore-color:#f8f8f8}button.primary:hover,button.primary:focus,[type="button"].primary:hover,[type="button"].primary:focus,[type="submit"].primary:hover,[type="submit"].primary:focus,[type="reset"].primary:hover,[type="reset"].primary:focus,.button.primary:hover,.button.primary:focus,[role="button"].primary:hover,[role="button"].primary:focus{--button-hover-back-color:#1565c0}button.secondary,[type="button"].secondary,[type="submit"].secondary,[type="reset"].secondary,.button.secondary,[role="button"].secondary{--button-back-color:#d32f2f;--button-fore-color:#f8f8f8}button.secondary:hover,button.secondary:focus,[type="button"].secondary:hover,[type="button"].secondary:focus,[type="submit"].secondary:hover,[type="submit"].secondary:focus,[type="reset"].secondary:hover,[type="reset"].secondary:focus,.button.secondary:hover,.button.secondary:focus,[role="button"].secondary:hover,[role="button"].secondary:focus{--button-hover-back-color:#c62828}button.tertiary,[type="button"].tertiary,[type="submit"].tertiary,[type="reset"].tertiary,.button.tertiary,[role="button"].tertiary{--button-back-color:#308732;--button-fore-color:#f8f8f8}button.tertiary:hover,button.tertiary:focus,[type="button"].tertiary:hover,[type="button"].tertiary:focus,[type="submit"].tertiary:hover,[type="submit"].tertiary:focus,[type="reset"].tertiary:hover,[type="reset"].tertiary:focus,.button.tertiary:hover,.button.tertiary:focus,[role="button"].tertiary:hover,[role="button"].tertiary:focus{--button-hover-back-color:#277529}button.inverse,[type="button"].inverse,[type="submit"].inverse,[type="reset"].inverse,.button.inverse,[role="button"].inverse{--button-back-color:#212121;--button-fore-color:#f8f8f8}button.inverse:hover,button.inverse:focus,[type="button"].inverse:hover,[type="button"].inverse:focus,[type="submit"].inverse:hover,[type="submit"].inverse:focus,[type="reset"].inverse:hover,[type="reset"].inverse:focus,.button.inverse:hover,.button.inverse:focus,[role="button"].inverse:hover,[role="button"].inverse:focus{--button-hover-back-color:#111}button.small,[type="button"].small,[type="submit"].small,[type="reset"].small,.button.small,[role="button"].small{padding:calc(0.5 * var(--universal-padding)) calc(0.75 * var(--universal-padding));margin:var(--universal-margin)}button.large,[type="button"].large,[type="submit"].large,[type="reset"].large,.button.large,[role="button"].large{padding:calc(1.5 * var(--universal-padding)) calc(2 * var(--universal-padding));margin:var(--universal-margin)}:root{--header-back-color:#f8f8f8;--header-hover-back-color:#f0f0f0;--header-fore-color:#444;--header-border-color:#ddd;--nav-back-color:#f8f8f8;--nav-hover-back-color:#f0f0f0;--nav-fore-color:#444;--nav-border-color:#ddd;--nav-link-color:#0277bd;--footer-fore-color:#444;--footer-back-color:#f8f8f8;--footer-border-color:#ddd;--footer-link-color:#0277bd;--drawer-back-color:#f8f8f8;--drawer-hover-back-color:#f0f0f0;--drawer-border-color:#ddd;--drawer-close-color:#444}header{height:3.1875rem;background:var(--header-back-color);color:var(--header-fore-color);border-bottom:.0625rem solid var(--header-border-color);padding:calc(var(--universal-padding) / 4) 0;white-space:nowrap;overflow-x:auto;overflow-y:hidden}header.row{box-sizing:content-box}header .logo{color:var(--header-fore-color);font-size:1.75rem;padding:var(--universal-padding) calc(2 * var(--universal-padding));text-decoration:none}header button,header [type="button"],header .button,header [role="button"]{box-sizing:border-box;position:relative;top:calc(0rem - var(--universal-padding) / 4);height:calc(3.1875rem + var(--universal-padding) / 2);background:var(--header-back-color);line-height:calc(3.1875rem - var(--universal-padding) * 1.5);text-align:center;color:var(--header-fore-color);border:0;border-radius:0;margin:0;text-transform:uppercase}header button:hover,header button:focus,header [type="button"]:hover,header [type="button"]:focus,header .button:hover,header .button:focus,header [role="button"]:hover,header [role="button"]:focus{background:var(--header-hover-back-color)}nav{background:var(--nav-back-color);color:var(--nav-fore-color);border:.0625rem solid var(--nav-border-color);border-radius:var(--universal-border-radius);margin:var(--universal-margin)}nav *{padding:var(--universal-padding) calc(1.5 * var(--universal-padding))}nav a,nav a:visited{display:block;color:var(--nav-link-color);border-radius:var(--universal-border-radius);transition:background 0.3s}nav a:hover,nav a:focus,nav a:visited:hover,nav a:visited:focus{text-decoration:none;background:var(--nav-hover-back-color)}nav .sublink-1{position:relative;margin-left:calc(2 * var(--universal-padding))}nav .sublink-1:before{position:absolute;left:calc(var(--universal-padding) - 1 * var(--universal-padding));top:-.0625rem;content:'';height:100%;border:.0625rem solid var(--nav-border-color);border-left:0}nav .sublink-2{position:relative;margin-left:calc(4 * var(--universal-padding))}nav .sublink-2:before{position:absolute;left:calc(var(--universal-padding) - 3 * var(--universal-padding));top:-.0625rem;content:'';height:100%;border:.0625rem solid var(--nav-border-color);border-left:0}footer{background:var(--footer-back-color);color:var(--footer-fore-color);border-top:.0625rem solid var(--footer-border-color);padding:calc(2 * var(--universal-padding)) var(--universal-padding);font-size:.875rem}footer a,footer a:visited{color:var(--footer-link-color)}header.sticky{position:-webkit-sticky;position:sticky;z-index:1101;top:0}footer.sticky{position:-webkit-sticky;position:sticky;z-index:1101;bottom:0}.drawer-toggle:before{display:inline-block;position:relative;vertical-align:bottom;content:'\00a0\2261\00a0';font-family:sans-serif;font-size:1.5em}@media screen and (min-width: 768px){.drawer-toggle:not(.persistent){display:none}}[type="checkbox"].drawer{height:1px;width:1px;margin:-1px;overflow:hidden;position:absolute;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%)}[type="checkbox"].drawer+*{display:block;box-sizing:border-box;position:fixed;top:0;width:320px;height:100vh;overflow-y:auto;background:var(--drawer-back-color);border:.0625rem solid var(--drawer-border-color);border-radius:0;margin:0;z-index:1110;right:-320px;transition:right 0.3s}[type="checkbox"].drawer+* .drawer-close{position:absolute;top:var(--universal-margin);right:var(--universal-margin);z-index:1111;width:2rem;height:2rem;border-radius:var(--universal-border-radius);padding:var(--universal-padding);margin:0;cursor:pointer;transition:background 0.3s}[type="checkbox"].drawer+* .drawer-close:before{display:block;content:'\00D7';color:var(--drawer-close-color);position:relative;font-family:sans-serif;font-size:2rem;line-height:1;text-align:center}[type="checkbox"].drawer+* .drawer-close:hover,[type="checkbox"].drawer+* .drawer-close:focus{background:var(--drawer-hover-back-color)}@media screen and (max-width: 320px){[type="checkbox"].drawer+*{width:100%}}[type="checkbox"].drawer:checked+*{right:0}@media screen and (min-width: 768px){[type="checkbox"].drawer:not(.persistent)+*{position:static;height:100%;z-index:1100}[type="checkbox"].drawer:not(.persistent)+* .drawer-close{display:none}}:root{--table-border-color:#aaa;--table-border-separator-color:#666;--table-head-back-color:#e6e6e6;--table-head-fore-color:#111;--table-body-back-color:#f8f8f8;--table-body-fore-color:#111;--table-body-alt-back-color:#eee}table{border-collapse:separate;border-spacing:0;margin:0;display:flex;flex:0 1 auto;flex-flow:row wrap;padding:var(--universal-padding);padding-top:0}table caption{font-size:1.5rem;margin:calc(2 * var(--universal-margin)) 0;max-width:100%;flex:0 0 100%}table thead,table tbody{display:flex;flex-flow:row wrap;border:.0625rem solid var(--table-border-color)}table thead{z-index:999;border-radius:var(--universal-border-radius) var(--universal-border-radius) 0 0;border-bottom:.0625rem solid var(--table-border-separator-color)}table tbody{border-top:0;margin-top:calc(0 - var(--universal-margin));border-radius:0 0 var(--universal-border-radius) var(--universal-border-radius)}table tr{display:flex;padding:0}table th,table td{padding:calc(2 * var(--universal-padding))}table th{text-align:left;background:var(--table-head-back-color);color:var(--table-head-fore-color)}table td{background:var(--table-body-back-color);color:var(--table-body-fore-color);border-top:.0625rem solid var(--table-border-color)}table:not(.horizontal){overflow:auto;max-height:400px}table:not(.horizontal) thead,table:not(.horizontal) tbody{max-width:100%;flex:0 0 100%}table:not(.horizontal) tr{flex-flow:row wrap;flex:0 0 100%}table:not(.horizontal) th,table:not(.horizontal) td{flex:1 0 0%;overflow:hidden;text-overflow:ellipsis}table:not(.horizontal) thead{position:sticky;top:0}table:not(.horizontal) tbody tr:first-child td{border-top:0}table.horizontal{border:0}table.horizontal thead,table.horizontal tbody{border:0;flex:.2 0 0;flex-flow:row nowrap}table.horizontal tbody{overflow:auto;justify-content:space-between;flex:.8 0 0;margin-left:0;padding-bottom:calc(var(--universal-padding) / 4)}table.horizontal tr{flex-direction:column;flex:1 0 auto}table.horizontal th,table.horizontal td{width:auto;border:0;border-bottom:.0625rem solid var(--table-border-color)}table.horizontal th:not(:first-child),table.horizontal td:not(:first-child){border-top:0}table.horizontal th{text-align:right;border-left:.0625rem solid var(--table-border-color);border-right:.0625rem solid var(--table-border-separator-color)}table.horizontal thead tr:first-child{padding-left:0}table.horizontal th:first-child,table.horizontal td:first-child{border-top:.0625rem solid var(--table-border-color)}table.horizontal tbody tr:last-child td{border-right:.0625rem solid var(--table-border-color)}table.horizontal tbody tr:last-child td:first-child{border-top-right-radius:0.25rem}table.horizontal tbody tr:last-child td:last-child{border-bottom-right-radius:0.25rem}table.horizontal thead tr:first-child th:first-child{border-top-left-radius:0.25rem}table.horizontal thead tr:first-child th:last-child{border-bottom-left-radius:0.25rem}@media screen and (max-width: 767px){table,table.horizontal{border-collapse:collapse;border:0;width:100%;display:table}table thead,table th,table.horizontal thead,table.horizontal th{border:0;height:1px;width:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%)}table tbody,table.horizontal tbody{border:0;display:table-row-group}table tr,table.horizontal tr{display:block;border:.0625rem solid var(--table-border-color);border-radius:var(--universal-border-radius);background:#fafafa;padding:var(--universal-padding);margin:var(--universal-margin);margin-bottom:calc(2 * var(--universal-margin))}table th,table td,table.horizontal th,table.horizontal td{width:auto}table td,table.horizontal td{display:block;border:0;text-align:right}table td:before,table.horizontal td:before{content:attr(data-label);float:left;font-weight:600}table th:first-child,table td:first-child,table.horizontal th:first-child,table.horizontal td:first-child{border-top:0}table tbody tr:last-child td,table.horizontal tbody tr:last-child td{border-right:0}}:root{--table-body-alt-back-color:#eee}table.striped tr:nth-of-type(2n)>td{background:var(--table-body-alt-back-color)}@media screen and (max-width: 768px){table.striped tr:nth-of-type(2n){background:var(--table-body-alt-back-color)}}:root{--table-body-hover-back-color:#90caf9}table.hoverable tr:hover,table.hoverable tr:hover>td,table.hoverable tr:focus,table.hoverable tr:focus>td{background:var(--table-body-hover-back-color)}@media screen and (max-width: 768px){table.hoverable tr:hover,table.hoverable tr:hover>td,table.hoverable tr:focus,table.hoverable tr:focus>td{background:var(--table-body-hover-back-color)}}:root{--mark-back-color:#0277bd;--mark-fore-color:#fafafa}mark{background:var(--mark-back-color);color:var(--mark-fore-color);font-size:.95em;line-height:1em;border-radius:var(--universal-border-radius);padding:calc(var(--universal-padding) / 4) calc(var(--universal-padding) / 2)}mark.inline-block{display:inline-block;font-size:1em;line-height:1.5;padding:calc(var(--universal-padding) / 2) var(--universal-padding)}:root{--toast-back-color:#424242;--toast-fore-color:#fafafa}.toast{position:fixed;bottom:calc(var(--universal-margin) * 3);left:50%;transform:translate(-50%, -50%);z-index:1111;color:var(--toast-fore-color);background:var(--toast-back-color);border-radius:calc(var(--universal-border-radius) * 16);padding:var(--universal-padding) calc(var(--universal-padding) * 3)}:root{--tooltip-back-color:#212121;--tooltip-fore-color:#fafafa}.tooltip{position:relative;display:inline-block}.tooltip:before,.tooltip:after{position:absolute;opacity:0;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%);transition:all 0.3s;z-index:1010;left:50%}.tooltip:not(.bottom):before,.tooltip:not(.bottom):after{bottom:75%}.tooltip.bottom:before,.tooltip.bottom:after{top:75%}.tooltip:hover:before,.tooltip:hover:after,.tooltip:focus:before,.tooltip:focus:after{opacity:1;clip:auto;-webkit-clip-path:inset(0%);clip-path:inset(0%)}.tooltip:before{content:'';background:transparent;border:var(--universal-margin) solid transparent;left:calc(50% - var(--universal-margin))}.tooltip:not(.bottom):before{border-top-color:#212121}.tooltip.bottom:before{border-bottom-color:#212121}.tooltip:after{content:attr(aria-label);color:var(--tooltip-fore-color);background:var(--tooltip-back-color);border-radius:var(--universal-border-radius);padding:var(--universal-padding);white-space:nowrap;transform:translateX(-50%)}.tooltip:not(.bottom):after{margin-bottom:calc(2 * var(--universal-margin))}.tooltip.bottom:after{margin-top:calc(2 * var(--universal-margin))}:root{--modal-overlay-color:rgba(0,0,0,0.45);--modal-close-color:#444;--modal-close-hover-color:#f0f0f0}[type="checkbox"].modal{height:1px;width:1px;margin:-1px;overflow:hidden;position:absolute;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%)}[type="checkbox"].modal+div{position:fixed;top:0;left:0;display:none;width:100vw;height:100vh;background:var(--modal-overlay-color)}[type="checkbox"].modal+div .card{margin:0 auto;max-height:50vh;overflow:auto}[type="checkbox"].modal+div .card .modal-close{position:absolute;top:0;right:0;width:1.75rem;height:1.75rem;border-radius:var(--universal-border-radius);padding:var(--universal-padding);margin:0;cursor:pointer;transition:background 0.3s}[type="checkbox"].modal+div .card .modal-close:before{display:block;content:'\00D7';color:var(--modal-close-color);position:relative;font-family:sans-serif;font-size:1.75rem;line-height:1;text-align:center}[type="checkbox"].modal+div .card .modal-close:hover,[type="checkbox"].modal+div .card .modal-close:focus{background:var(--modal-close-hover-color)}[type="checkbox"].modal:checked+div{display:flex;flex:0 1 auto;z-index:1200}[type="checkbox"].modal:checked+div .card .modal-close{z-index:1211}:root{--collapse-label-back-color:#e8e8e8;--collapse-label-fore-color:#212121;--collapse-label-hover-back-color:#f0f0f0;--collapse-selected-label-back-color:#ececec;--collapse-border-color:#ddd;--collapse-content-back-color:#fafafa;--collapse-selected-label-border-color:#0277bd}.collapse{width:calc(100% - 2 * var(--universal-margin));opacity:1;display:flex;flex-direction:column;margin:var(--universal-margin);border-radius:var(--universal-border-radius)}.collapse>[type="radio"],.collapse>[type="checkbox"]{height:1px;width:1px;margin:-1px;overflow:hidden;position:absolute;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%)}.collapse>label{flex-grow:1;display:inline-block;height:1.5rem;cursor:pointer;transition:background 0.3s;color:var(--collapse-label-fore-color);background:var(--collapse-label-back-color);border:.0625rem solid var(--collapse-border-color);padding:calc(1.5 * var(--universal-padding))}.collapse>label:hover,.collapse>label:focus{background:var(--collapse-label-hover-back-color)}.collapse>label+div{flex-basis:auto;height:1px;width:1px;margin:-1px;overflow:hidden;position:absolute;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%);transition:max-height 0.3s;max-height:1px}.collapse>:checked+label{background:var(--collapse-selected-label-back-color);border-bottom-color:var(--collapse-selected-label-border-color)}.collapse>:checked+label+div{box-sizing:border-box;position:relative;width:100%;height:auto;overflow:auto;margin:0;background:var(--collapse-content-back-color);border:.0625rem solid var(--collapse-border-color);border-top:0;padding:var(--universal-padding);clip:auto;-webkit-clip-path:inset(0%);clip-path:inset(0%);max-height:400px}.collapse>label:not(:first-of-type){border-top:0}.collapse>label:first-of-type{border-radius:var(--universal-border-radius) var(--universal-border-radius) 0 0}.collapse>label:last-of-type:not(:first-of-type){border-radius:0 0 var(--universal-border-radius) var(--universal-border-radius)}.collapse>label:last-of-type:first-of-type{border-radius:var(--universal-border-radius)}.collapse>:checked:last-of-type:not(:first-of-type)+label{border-radius:0}.collapse>:checked:last-of-type+label+div{border-radius:0 0 var(--universal-border-radius) var(--universal-border-radius)}mark.secondary{--mark-back-color:#d32f2f}mark.tertiary{--mark-back-color:#308732}mark.tag{padding:calc(var(--universal-padding)/2) var(--universal-padding);border-radius:1em}:root{--progress-back-color:#ddd;--progress-fore-color:#555}progress{display:block;vertical-align:baseline;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:.75rem;width:calc(100% - 2 * var(--universal-margin));margin:var(--universal-margin);border:0;border-radius:calc(2 * var(--universal-border-radius));background:var(--progress-back-color);color:var(--progress-fore-color)}progress::-webkit-progress-value{background:var(--progress-fore-color);border-top-left-radius:calc(2 * var(--universal-border-radius));border-bottom-left-radius:calc(2 * var(--universal-border-radius))}progress::-webkit-progress-bar{background:var(--progress-back-color)}progress::-moz-progress-bar{background:var(--progress-fore-color);border-top-left-radius:calc(2 * var(--universal-border-radius));border-bottom-left-radius:calc(2 * var(--universal-border-radius))}progress[value="1000"]::-webkit-progress-value{border-radius:calc(2 * var(--universal-border-radius))}progress[value="1000"]::-moz-progress-bar{border-radius:calc(2 * var(--universal-border-radius))}progress.inline{display:inline-block;vertical-align:middle;width:60%}:root{--spinner-back-color:#ddd;--spinner-fore-color:#555}@keyframes spinner-donut-anim{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.spinner{display:inline-block;margin:var(--universal-margin);border:.25rem solid var(--spinner-back-color);border-left:.25rem solid var(--spinner-fore-color);border-radius:50%;width:1.25rem;height:1.25rem;animation:spinner-donut-anim 1.2s linear infinite}progress.primary{--progress-fore-color:#1976d2}progress.secondary{--progress-fore-color:#d32f2f}progress.tertiary{--progress-fore-color:#308732}.spinner.primary{--spinner-fore-color:#1976d2}.spinner.secondary{--spinner-fore-color:#d32f2f}.spinner.tertiary{--spinner-fore-color:#308732}span[class^='icon-']{display:inline-block;height:1em;width:1em;vertical-align:-0.125em;background-size:contain;margin:0 calc(var(--universal-margin) / 4)}span[class^='icon-'].secondary{-webkit-filter:invert(25%);filter:invert(25%)}span[class^='icon-'].inverse{-webkit-filter:invert(100%);filter:invert(100%)}span.icon-alert{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12' y2='16'%3E%3C/line%3E%3C/svg%3E")}span.icon-bookmark{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z'%3E%3C/path%3E%3C/svg%3E")}span.icon-calendar{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E")}span.icon-credit{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='1' y='4' width='22' height='16' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='1' y1='10' x2='23' y2='10'%3E%3C/line%3E%3C/svg%3E")}span.icon-edit{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34'%3E%3C/path%3E%3Cpolygon points='18 2 22 6 12 16 8 16 8 12 18 2'%3E%3C/polygon%3E%3C/svg%3E")}span.icon-link{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'%3E%3C/path%3E%3Cpolyline points='15 3 21 3 21 9'%3E%3C/polyline%3E%3Cline x1='10' y1='14' x2='21' y2='3'%3E%3C/line%3E%3C/svg%3E")}span.icon-help{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='17' x2='12' y2='17'%3E%3C/line%3E%3C/svg%3E")}span.icon-home{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z'%3E%3C/path%3E%3Cpolyline points='9 22 9 12 15 12 15 22'%3E%3C/polyline%3E%3C/svg%3E")}span.icon-info{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='16' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='8' x2='12' y2='8'%3E%3C/line%3E%3C/svg%3E")}span.icon-lock{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='11' width='18' height='11' rx='2' ry='2'%3E%3C/rect%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'%3E%3C/path%3E%3C/svg%3E")}span.icon-mail{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z'%3E%3C/path%3E%3Cpolyline points='22,6 12,13 2,6'%3E%3C/polyline%3E%3C/svg%3E")}span.icon-location{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z'%3E%3C/path%3E%3Ccircle cx='12' cy='10' r='3'%3E%3C/circle%3E%3C/svg%3E")}span.icon-phone{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z'%3E%3C/path%3E%3C/svg%3E")}span.icon-rss{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 11a9 9 0 0 1 9 9'%3E%3C/path%3E%3Cpath d='M4 4a16 16 0 0 1 16 16'%3E%3C/path%3E%3Ccircle cx='5' cy='19' r='1'%3E%3C/circle%3E%3C/svg%3E")}span.icon-search{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E")}span.icon-settings{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'%3E%3C/circle%3E%3Cpath d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z'%3E%3C/path%3E%3C/svg%3E")}span.icon-share{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='18' cy='5' r='3'%3E%3C/circle%3E%3Ccircle cx='6' cy='12' r='3'%3E%3C/circle%3E%3Ccircle cx='18' cy='19' r='3'%3E%3C/circle%3E%3Cline x1='8.59' y1='13.51' x2='15.42' y2='17.49'%3E%3C/line%3E%3Cline x1='15.41' y1='6.51' x2='8.59' y2='10.49'%3E%3C/line%3E%3C/svg%3E")}span.icon-cart{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='21' r='1'%3E%3C/circle%3E%3Ccircle cx='20' cy='21' r='1'%3E%3C/circle%3E%3Cpath d='M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6'%3E%3C/path%3E%3C/svg%3E")}span.icon-upload{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'%3E%3C/path%3E%3Cpolyline points='17 8 12 3 7 8'%3E%3C/polyline%3E%3Cline x1='12' y1='3' x2='12' y2='15'%3E%3C/line%3E%3C/svg%3E")}span.icon-user{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23111' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E")}:root{--generic-border-color:rgba(0,0,0,0.3);--generic-box-shadow:0 .25rem .25rem 0 rgba(0,0,0,0.125),0 .125rem .125rem -.125rem rgba(0,0,0,0.25)}.hidden{display:none !important}.visually-hidden{position:absolute !important;width:1px !important;height:1px !important;margin:-1px !important;border:0 !important;padding:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(100%) !important;clip-path:inset(100%) !important;overflow:hidden !important}.bordered{border:.0625rem solid var(--generic-border-color) !important}.rounded{border-radius:var(--universal-border-radius) !important}.circular{border-radius:50% !important}.shadowed{box-shadow:var(--generic-box-shadow) !important}.responsive-margin{margin:calc(var(--universal-margin) / 4) !important}@media screen and (min-width: 768px){.responsive-margin{margin:calc(var(--universal-margin) / 2) !important}}@media screen and (min-width: 1280px){.responsive-margin{margin:var(--universal-margin) !important}}.responsive-padding{padding:calc(var(--universal-padding) / 4) !important}@media screen and (min-width: 768px){.responsive-padding{padding:calc(var(--universal-padding) / 2) !important}}@media screen and (min-width: 1280px){.responsive-padding{padding:var(--universal-padding) !important}}@media screen and (max-width: 767px){.hidden-sm{display:none !important}}@media screen and (min-width: 768px) and (max-width: 1279px){.hidden-md{display:none !important}}@media screen and (min-width: 1280px){.hidden-lg{display:none !important}}@media screen and (max-width: 767px){.visually-hidden-sm{position:absolute !important;width:1px !important;height:1px !important;margin:-1px !important;border:0 !important;padding:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(100%) !important;clip-path:inset(100%) !important;overflow:hidden !important}}@media screen and (min-width: 768px) and (max-width: 1279px){.visually-hidden-md{position:absolute !important;width:1px !important;height:1px !important;margin:-1px !important;border:0 !important;padding:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(100%) !important;clip-path:inset(100%) !important;overflow:hidden !important}}@media screen and (min-width: 1280px){.visually-hidden-lg{position:absolute !important;width:1px !important;height:1px !important;margin:-1px !important;border:0 !important;padding:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(100%) !important;clip-path:inset(100%) !important;overflow:hidden !important}}
--- a/themes/minimal/400.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h1>Bad request</h1>
--- a/themes/minimal/404.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h1>Not found</h1>
--- a/themes/minimal/500.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h1>Internal server error</h1>
--- a/themes/minimal/footer.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-	<body>
-</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/fragments/duration.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<option value="@@duration@@">@@duration@@</option>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/fragments/footer.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,2 @@
+	<body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/fragments/header.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+	</head>
+	<body>
+
+	<h1>Menu</h1>
+	<ul>
+		<li><a href="/">Home</a></li>
+		<li><a href="/new">New</a></li>
+		<li><a href="/search">Search</a></li>
+	</ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/fragments/language.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<option value="@@language@@" @@selected@@>@@language@@</option>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/fragments/paste.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,7 @@
+<tr>
+	<td><a href="/paste/@@id@@">@@title@@</a></td>
+	<td>@@author@@</td>
+	<td>@@language@@</td>
+	<td>@@date@@</td>
+	<td>@@expiration@@</td>
+</tr>
--- a/themes/minimal/header.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<html>
-	<head>
-		<meta charset="UTF-8">
-	</head>
-	<body>
-
-	<h1>Menu</h1>
-	<ul>
-		<li><a href="/">Home</a></li>
-		<li><a href="/new">New</a></li>
-		<li><a href="/search">Search</a></li>
-	</ul>
--- a/themes/minimal/index-paste.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-<tr>
-	<td><a href="/paste/@@id@@">@@name@@</a></td>
-	<td>@@author@@</td>
-	<td>@@language@@</td>
-	<td>@@date@@</td>
-	<td>@@expiration@@</td>
-</tr>
--- a/themes/minimal/index.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-	<h1>Recent pastes</h1>
-
-	<table>
-		<thead>
-			<tr>
-				<th>Name</th>
-				<th>Author</th>
-				<th>Language</th>
-				<th>Date</th>
-				<th>Expires in</th>
-			<tr>
-		</thead>
-	<tbody>
-
-	@@pastes@@
-
-	</tbody>
--- a/themes/minimal/new.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-	<h1>Create paste</h1>
-
-	<form action="/new" method="post">
-		<table>
-			<tr>
-				<td>Title</td>
-				<td><input name="title" type="text" placeholder="Untitled" value="@@title@@" /></td>
-			</tr>
-
-			<tr>
-				<td>Author</td>
-				<td><input name="author" type="text" placeholder="Anonymous" value="@@author@@" /></td>
-			</tr>
-
-			<tr>
-				<td>Language</td>
-				<td>
-					<select name="language">
-						@@languages@@
-					</select>
-				</td>
-			</tr>
-
-			<tr>
-				<td>Expires in</td>
-				<td>
-					<select name="duration">
-						@@durations@@
-					</select>
-				</td>
-			</tr>
-
-			<tr>
-				<td>Private</td>
-				<td><input type="checkbox" name="private" @@private@@></input></td>
-			</tr>
-		</table>
-
-		<textarea id="code" class="textarea" placeholder="What do you want to share?" rows="10" name="code">@@code@@</textarea>
-		<button>Submit</button>
-	</form>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/400.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Bad request</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/404.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Not found</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/500.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Internal server error</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/index.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,17 @@
+	<h1>Recent pastes</h1>
+
+	<table>
+		<thead>
+			<tr>
+				<th>Name</th>
+				<th>Author</th>
+				<th>Language</th>
+				<th>Date</th>
+				<th>Expires in</th>
+			<tr>
+		</thead>
+	<tbody>
+
+	@@pastes@@
+
+	</tbody>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/new.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,41 @@
+	<h1>Create paste</h1>
+
+	<form action="/new" method="post">
+		<table>
+			<tr>
+				<td>Title</td>
+				<td><input name="title" type="text" placeholder="Untitled" value="@@title@@" /></td>
+			</tr>
+
+			<tr>
+				<td>Author</td>
+				<td><input name="author" type="text" placeholder="Anonymous" /></td>
+			</tr>
+
+			<tr>
+				<td>Language</td>
+				<td>
+					<select name="language">
+						@@languages@@
+					</select>
+				</td>
+			</tr>
+
+			<tr>
+				<td>Expires in</td>
+				<td>
+					<select name="duration">
+						@@durations@@
+					</select>
+				</td>
+			</tr>
+
+			<tr>
+				<td>Private</td>
+				<td><input type="checkbox" name="private" checked></input></td>
+			</tr>
+		</table>
+
+		<textarea id="code" class="textarea" placeholder="What do you want to share?" rows="10" name="code">@@code@@</textarea>
+		<button>Submit</button>
+	</form>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/paste.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,41 @@
+	<h2>Actions</h2>
+	<ul>
+		<li><a href="/fork/@@id@@">Fork</a></li>
+		<li><a href="/download/@@id@@">Download</a></li>
+	</ul>
+
+	<table>
+		<tbody>
+			<tr>
+				<td>Identifier</td>
+				<td>@@id@@</td>
+			</tr>
+			<tr>
+				<td>Title</td>
+				<td>@@title@@</td>
+			</tr>
+			<tr>
+				<td>Author</td>
+				<td>@@author@@</td>
+			</tr>
+			<tr>
+				<td>Language</td>
+				<td>@@language@@</td>
+			</tr>
+			<tr>
+				<td>Date</td>
+				<td>@@date@@</td>
+			</tr>
+			<tr>
+				<td>Is visible?</td>
+				<td>@@visible@@</td>
+			</tr>
+			<tr>
+				<td>Lifetime</td>
+				<td>@@expiration@@</td>
+			</tr>
+		</tbody>
+	</table>
+	<pre>
+@@code@@
+	</pre>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/minimal/pages/search.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,26 @@
+	<h1>Search pastes</h1>
+
+	<form action="/search" method="post">
+		<table>
+			<tr>
+				<td>Title</td>
+				<td><input name="title" type="text" placeholder="Title" /></td>
+			</tr>
+
+			<tr>
+				<td>Author</td>
+				<td><input name="author" type="text" placeholder="Author" /></td>
+			</tr>
+
+			<tr>
+				<td>Language</td>
+				<td>
+					<select name="language">
+						@@languages@@
+					</select>
+				</td>
+			</tr>
+		</table>
+
+		<button>Submit</button>
+	</form>
--- a/themes/minimal/paste.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-	<h2>Actions</h2>
-	<ul>
-		<li><a href="/fork/@@id@@">Fork</a></li>
-		<li><a href="/download/@@id@@">Download</a></li>
-	</ul>
-
-	<table>
-		<tbody>
-			<tr>
-				<td>Identifier</td>
-				<td>@@id@@</td>
-			</tr>
-			<tr>
-				<td>Title</td>
-				<td>@@title@@</td>
-			</tr>
-			<tr>
-				<td>Author</td>
-				<td>@@author@@</td>
-			</tr>
-			<tr>
-				<td>Language</td>
-				<td>@@language@@</td>
-			</tr>
-			<tr>
-				<td>Date</td>
-				<td>@@timestamp@@</td>
-			</tr>
-			<tr>
-				<td>Is visible?</td>
-				<td>@@visible@@</td>
-			</tr>
-			<tr>
-				<td>Lifetime</td>
-				<td>@@duration@@</td>
-			</tr>
-		</tbody>
-	</table>
-	<pre>
-@@code@@
-	</pre>
--- a/themes/minimal/search.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-	<h1>Search pastes</h1>
-
-	<form action="/search" method="post">
-		<table>
-			<tr>
-				<td>Title</td>
-				<td><input name="title" type="text" placeholder="Title" /></td>
-			</tr>
-
-			<tr>
-				<td>Author</td>
-				<td><input name="author" type="text" placeholder="Author" /></td>
-			</tr>
-
-			<tr>
-				<td>Language</td>
-				<td>
-					<select name="language">
-						@@languages@@
-					</select>
-				</td>
-			</tr>
-		</table>
-
-		<button>Submit</button>
-	</form>
--- a/themes/siimple/400.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h1>Bad request</h1>
--- a/themes/siimple/404.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-<h1>Not found</h1>
-
--- a/themes/siimple/500.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-<h1>Internal error</h1>
--- a/themes/siimple/footer.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-		</div>
-	<body>
-</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/fragments/duration.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<option value="@@duration@@">@@duration@@</option>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/fragments/footer.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,3 @@
+		</div>
+	<body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/fragments/header.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<link rel="stylesheet" href="/static/siimple.min.css" />
+		<link rel="stylesheet" href="/static/styles/github.css" />
+		<script src="/static/highlight.min.js"></script>
+		<script>hljs.initHighlightingOnLoad();</script>
+	</head>
+
+	<body>
+		<div class="siimple-navbar">
+			<a class="siimple-navbar-title" href="/">paster</a>
+			<div class="siimple-navbar-subtitle">simple paste service</div>
+			<div class="siimple--float-right">
+				<a class="siimple-navbar-item" href="/new">Create</a>
+				<a class="siimple-navbar-item" href="/search">Search</a>
+			</div>
+		</div>
+
+		<div class="siimple-content siimple-content--large">
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/fragments/language.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<option value="@@language@@" @@selected@@>@@language@@</option>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/fragments/paste.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,7 @@
+<div class="siimple-table-row">
+	<div class="siimple-table-cell"><a class="siimple-link" href="/paste/@@id@@">@@id@@</a></div>
+	<div class="siimple-table-cell">@@author@@</div>
+	<div class="siimple-table-cell">@@language@@</div>
+	<div class="siimple-table-cell">@@date@@</div>
+	<div class="siimple-table-cell">@@expiration@@</div>
+</div>
--- a/themes/siimple/header.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-	<head>
-		<meta charset="UTF-8">
-		<link rel="stylesheet" href="/static/siimple.min.css" />
-		<link rel="stylesheet" href="/static/styles/github.css" />
-		<script src="/static/highlight.min.js"></script>
-		<script>hljs.initHighlightingOnLoad();</script>
-	</head>
-
-	<body>
-		<div class="siimple-navbar">
-			<a class="siimple-navbar-title" href="/">paster</a>
-			<div class="siimple-navbar-subtitle">simple paste service</div>
-			<div class="siimple--float-right">
-				<a class="siimple-navbar-item" href="/new">Create</a>
-				<a class="siimple-navbar-item" href="/search">Search</a>
-			</div>
-		</div>
-
-		<div class="siimple-content siimple-content--large">
-
--- a/themes/siimple/index-paste.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-<div class="siimple-table-row">
-	<div class="siimple-table-cell"><a class="siimple-link" href="/paste/@@id@@">@@name@@</a></div>
-	<div class="siimple-table-cell">@@author@@</div>
-	<div class="siimple-table-cell">@@language@@</div>
-	<div class="siimple-table-cell">@@date@@</div>
-	<div class="siimple-table-cell">@@expiration@@</div>
-</div>
--- a/themes/siimple/index.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-	<h1>Recent pastes</h1>
-
-	<div class="siimple-table">
-		<div class="siimple-table-header">
-			<div class="siimple-table-row">
-				<div class="siimple-table-cell">Name</div>
-				<div class="siimple-table-cell">Author</div>
-				<div class="siimple-table-cell">Language</div>
-				<div class="siimple-table-cell">Date</div>
-				<div class="siimple-table-cell">Expires in</div>
-			</div>
-		</div>
-	<div class="siimple-table-body">
-
-	@@pastes@@
-
-	</div>
--- a/themes/siimple/new.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-	<h1>Create paste</h1>
-
-	<div class="siimple-form">
-		<form action="/new" method="post">
-			<div class="siimple-field">
-				<label class="siimple-field-label">Title</label>
-				<input class="siimple-input" name="title" type="text" placeholder="Untitled" value="@@title@@" />
-			</div>
-
-			<div class="siimple-field">
-				<label class="siimple-field-label">Author</label>
-				<input class="siimple-input" name="author" type="text" placeholder="Anonymous" value="@@author@@" />
-			</div>
-
-			<div class="siimple-field">
-				<label class="siimple-field-label">Language</label>
-				<select class="siimple-select" name="language">
-					@@languages@@
-				</select>
-			</div>
-
-			<div class="siimple-field">
-				<label class="siimple-field-label">Expires in</label>
-				<select class="siimple-select" name="duration">
-					@@durations@@
-				</select>
-			</div>
-
-			<div class="siimple-field">
-				<label class="siimple-label">Private</label>
-				<div class="siimple-checkbox">
-					<input type="checkbox" id="private" name="private" @@private@@>
-					<label for="private"></label>
-				</div>
-			</div>
-
-			<div class="siimple-field">
-				<textarea id="code" class="simple-textarea siimple-textarea--fluid" placeholder="What do you want to share?" rows="10" name="code">@@code@@</textarea>
-			</div>
-
-			<div class="siimple-form-field">
-				<input type="submit" class="siimple-btn siimple-btn--blue"></input>
-			</div>
-		</form>
-	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/400.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Bad request</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/404.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,2 @@
+<h1>Not found</h1>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/500.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,1 @@
+<h1>Internal error</h1>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/index.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,17 @@
+	<h1>Recent pastes</h1>
+
+	<div class="siimple-table">
+		<div class="siimple-table-header">
+			<div class="siimple-table-row">
+				<div class="siimple-table-cell">Name</div>
+				<div class="siimple-table-cell">Author</div>
+				<div class="siimple-table-cell">Language</div>
+				<div class="siimple-table-cell">Date</div>
+				<div class="siimple-table-cell">Expires in</div>
+			</div>
+		</div>
+	<div class="siimple-table-body">
+
+	@@pastes@@
+
+	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/new.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,45 @@
+	<h1>Create paste</h1>
+
+	<div class="siimple-form">
+		<form action="/new" method="post">
+			<div class="siimple-field">
+				<label class="siimple-field-label">Title</label>
+				<input class="siimple-input" name="title" type="text" placeholder="Untitled" value="@@title@@" />
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Author</label>
+				<input class="siimple-input" name="author" type="text" placeholder="Anonymous" />
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Language</label>
+				<select class="siimple-select" name="language">
+					@@languages@@
+				</select>
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Expires in</label>
+				<select class="siimple-select" name="duration">
+					@@durations@@
+				</select>
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-label">Private</label>
+				<div class="siimple-checkbox">
+					<input type="checkbox" id="private" name="private" checked>
+					<label for="private"></label>
+				</div>
+			</div>
+
+			<div class="siimple-field">
+				<textarea id="code" class="simple-textarea siimple-textarea--fluid" placeholder="What do you want to share?" rows="10" name="code">@@code@@</textarea>
+			</div>
+
+			<div class="siimple-form-field">
+				<input type="submit" class="siimple-btn siimple-btn--blue"></input>
+			</div>
+		</form>
+	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/paste.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,35 @@
+	<h1>@@title@@</h1>
+
+	<a class="siimple-btn siimple-btn--navy" href="/fork/@@id@@">Fork</a>
+	<a class="siimple-btn siimple-btn--navy" href="/download/@@id@@">Download</a>
+
+	<div class="siimple-grid">
+		<div class="siimple-grid-row">
+			<div class="siimple-grid-col siimple-grid-col--2"><strong>Identifier</strong></div>
+			<div class="siimple-grid-col siimple-grid-col--10">@@id@@</div>
+		</div>
+		<div class="siimple-grid-row">
+			<div class="siimple-grid-col siimple-grid-col--2"><strong>Author</strong></div>
+			<div class="siimple-grid-col siimple-grid-col--10">@@author@@</div>
+		</div>
+		<div class="siimple-grid-row">
+			<div class="siimple-grid-col siimple-grid-col--2"><strong>Language</strong></div>
+			<div class="siimple-grid-col siimple-grid-col--10">@@language@@</div>
+		</div>
+		<div class="siimple-grid-row">
+			<div class="siimple-grid-col siimple-grid-col--2"><strong>Date</strong></div>
+			<div class="siimple-grid-col siimple-grid-col--10">@@date@@</div>
+		</div>
+		<div class="siimple-grid-row">
+			<div class="siimple-grid-col siimple-grid-col--2"><strong>Is visible?</strong></div>
+			<div class="siimple-grid-col siimple-grid-col--10">@@visible@@</div>
+		</div>
+		<div class="siimple-grid-row">
+			<div class="siimple-grid-col siimple-grid-col--2"><strong>Lifetime</strong></div>
+			<div class="siimple-grid-col siimple-grid-col--10">@@expiration@@</div>
+		</div>
+	</div>
+
+	<pre><code class="@@language@@">
+@@code@@
+	</code></pre>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/siimple/pages/search.html	Mon Dec 21 18:22:44 2020 +0100
@@ -0,0 +1,26 @@
+	<h1>Search pastes</h1>
+
+	<div class="siimple-form">
+		<form action="/search" method="post">
+			<div class="siimple-field">
+				<label class="siimple-field-label">Title</label>
+				<input class="siimple-input" name="title" type="text" placeholder="Title" />
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Author</label>
+				<input class="siimple-input" name="author" type="text" placeholder="Author" />
+			</div>
+
+			<div class="siimple-field">
+				<label class="siimple-field-label">Language</label>
+				<select class="siimple-select" name="language">
+					@@languages@@
+				</select>
+			</div>
+
+			<div class="siimple-form-field">
+				<input type="submit" class="siimple-btn siimple-btn--blue"></input>
+			</div>
+		</form>
+	</div>
--- a/themes/siimple/paste.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-	<h1>@@title@@</h1>
-
-	<a class="siimple-btn siimple-btn--navy" href="/fork/@@id@@">Fork</a>
-	<a class="siimple-btn siimple-btn--navy" href="/download/@@id@@">Download</a>
-
-	<div class="siimple-grid">
-		<div class="siimple-grid-row">
-			<div class="siimple-grid-col siimple-grid-col--2"><strong>Identifier</strong></div>
-			<div class="siimple-grid-col siimple-grid-col--10">@@id@@</div>
-		</div>
-		<div class="siimple-grid-row">
-			<div class="siimple-grid-col siimple-grid-col--2"><strong>Author</strong></div>
-			<div class="siimple-grid-col siimple-grid-col--10">@@author@@</div>
-		</div>
-		<div class="siimple-grid-row">
-			<div class="siimple-grid-col siimple-grid-col--2"><strong>Language</strong></div>
-			<div class="siimple-grid-col siimple-grid-col--10">@@language@@</div>
-		</div>
-		<div class="siimple-grid-row">
-			<div class="siimple-grid-col siimple-grid-col--2"><strong>Date</strong></div>
-			<div class="siimple-grid-col siimple-grid-col--10">@@timestamp@@</div>
-		</div>
-		<div class="siimple-grid-row">
-			<div class="siimple-grid-col siimple-grid-col--2"><strong>Is visible?</strong></div>
-			<div class="siimple-grid-col siimple-grid-col--10">@@visible@@</div>
-		</div>
-		<div class="siimple-grid-row">
-			<div class="siimple-grid-col siimple-grid-col--2"><strong>Lifetime</strong></div>
-			<div class="siimple-grid-col siimple-grid-col--10">@@duration@@</div>
-		</div>
-	</div>
-
-	<pre><code class="@@language@@">
-@@code@@
-	</code></pre>
--- a/themes/siimple/search.html	Mon Apr 27 12:08:16 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-	<h1>Search pastes</h1>
-
-	<div class="siimple-form">
-		<form action="/search" method="post">
-			<div class="siimple-field">
-				<label class="siimple-field-label">Title</label>
-				<input class="siimple-input" name="title" type="text" placeholder="Title" />
-			</div>
-
-			<div class="siimple-field">
-				<label class="siimple-field-label">Author</label>
-				<input class="siimple-input" name="author" type="text" placeholder="Author" />
-			</div>
-
-			<div class="siimple-field">
-				<label class="siimple-field-label">Language</label>
-				<select class="siimple-select" name="language">
-					@@languages@@
-				</select>
-			</div>
-
-			<div class="siimple-form-field">
-				<input type="submit" class="siimple-btn siimple-btn--blue"></input>
-			</div>
-		</form>
-	</div>
--- a/util.c	Mon Apr 27 12:08:16 2020 +0200
+++ b/util.c	Mon Dec 21 18:22:44 2020 +0100
@@ -17,7 +17,9 @@
  */
 
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
+#include <limits.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -25,7 +27,191 @@
 #include <string.h>
 #include <time.h>
 
+#include "config.h"
 #include "util.h"
+#include "paste.h"
+
+const char *languages[] = {
+	"nohighlight",
+	"1c",
+	"abnf",
+	"accesslog",
+	"actionscript",
+	"ada",
+	"apache",
+	"applescript",
+	"arduino",
+	"armasm",
+	"asciidoc",
+	"aspectj",
+	"autohotkey",
+	"autoit",
+	"avrasm",
+	"awk",
+	"axapta",
+	"bash",
+	"basic",
+	"bnf",
+	"brainfuck",
+	"cal",
+	"capnproto",
+	"ceylon",
+	"clean",
+	"clojure",
+	"clojure-repl",
+	"cmake",
+	"coffeescript",
+	"coq",
+	"cos",
+	"cpp",
+	"crmsh",
+	"crystal",
+	"cs",
+	"csp",
+	"css",
+	"dart",
+	"delphi",
+	"diff",
+	"django",
+	"d",
+	"dns",
+	"dockerfile",
+	"dos",
+	"dsconfig",
+	"dts",
+	"dust",
+	"ebnf",
+	"elixir",
+	"elm",
+	"erb",
+	"erlang",
+	"erlang-repl",
+	"excel",
+	"fix",
+	"flix",
+	"fortran",
+	"fsharp",
+	"gams",
+	"gauss",
+	"gcode",
+	"gherkin",
+	"glsl",
+	"go",
+	"golo",
+	"gradle",
+	"groovy",
+	"haml",
+	"handlebars",
+	"haskell",
+	"haxe",
+	"hsp",
+	"htmlbars",
+	"http",
+	"hy",
+	"inform7",
+	"ini",
+	"irpf90",
+	"java",
+	"javascript",
+	"jboss-cli",
+	"json",
+	"julia",
+	"julia-repl",
+	"kotlin",
+	"lasso",
+	"ldif",
+	"leaf",
+	"less",
+	"lisp",
+	"livecodeserver",
+	"livescript",
+	"llvm",
+	"lsl",
+	"lua",
+	"makefile",
+	"markdown",
+	"mathematica",
+	"matlab",
+	"maxima",
+	"mel",
+	"mercury",
+	"mipsasm",
+	"mizar",
+	"mojolicious",
+	"monkey",
+	"moonscript",
+	"n1ql",
+	"nginx",
+	"nimrod",
+	"nix",
+	"nsis",
+	"objectivec",
+	"ocaml",
+	"openscad",
+	"oxygene",
+	"parser3",
+	"perl",
+	"pf",
+	"php",
+	"pony",
+	"powershell",
+	"processing",
+	"profile",
+	"prolog",
+	"protobuf",
+	"puppet",
+	"purebasic",
+	"python",
+	"q",
+	"qml",
+	"rib",
+	"r",
+	"roboconf",
+	"routeros",
+	"rsl",
+	"ruby",
+	"ruleslanguage",
+	"rust",
+	"scala",
+	"scheme",
+	"scilab",
+	"scss",
+	"shell",
+	"smali",
+	"smalltalk",
+	"sml",
+	"sqf",
+	"sql",
+	"stan",
+	"stata",
+	"step21",
+	"stylus",
+	"subunit",
+	"swift",
+	"taggerscript",
+	"tap",
+	"tcl",
+	"tex",
+	"thrift",
+	"tp",
+	"twig",
+	"typescript",
+	"vala",
+	"vbnet",
+	"vbscript-html",
+	"vbscript",
+	"verilog",
+	"vhdl",
+	"vim",
+	"x86asm",
+	"xl",
+	"xml",
+	"xquery",
+	"yaml",
+	"zephir"
+};
+
+const size_t languagesz = NELEM(languages);
 
 noreturn void
 die(const char *fmt, ...)
@@ -75,3 +261,47 @@
 
 	return buf;
 }
+
+const char *
+path(const char *filename)
+{
+	assert(filename);
+
+	/* Build path to the template file. */
+	static char path[PATH_MAX];
+
+	snprintf(path, sizeof (path), "%s/%s", config.themedir, filename);
+
+	return path;
+}
+
+void
+replace(char **dst, const char *s)
+{
+	assert(dst);
+	assert(s);
+
+	/* Trim leading spaces. */
+	while (*s && isspace(*s))
+		s++;
+
+	if (*s) {
+		free(*dst);
+		*dst = estrdup(s);
+	}
+}
+
+const char *
+ttl(time_t timestamp, long long int duration)
+{
+	const time_t now = time(NULL);
+	const long long int left = duration - difftime(now, timestamp);
+
+	if (left < PASTE_DURATION_HOUR)
+		return bprintf("%lld minute(s)", left / 60);
+	if (left < PASTE_DURATION_DAY)
+		return bprintf("%lld hour(s)", left / 3600);
+
+	/* Other in days. */
+	return bprintf("%lld day(s)", left / 86400);
+}
--- a/util.h	Mon Apr 27 12:08:16 2020 +0200
+++ b/util.h	Mon Dec 21 18:22:44 2020 +0100
@@ -19,10 +19,17 @@
 #ifndef PASTER_UTIL_H
 #define PASTER_UTIL_H
 
+#include <stddef.h>
 #include <stdnoreturn.h>
+#include <time.h>
+
+#define NELEM(x) (sizeof (x) / sizeof (x)[0])
 
 struct tm;
 
+extern const char *languages[];
+extern const size_t languagesz;
+
 noreturn void
 die(const char *, ...);
 
@@ -35,4 +42,13 @@
 const char *
 bstrftime(const char *, const struct tm *);
 
+const char *
+path(const char *);
+
+void
+replace(char **, const char *);
+
+const char *
+ttl(time_t, long long int);
+
 #endif /* !PASTER_UTIL_H */