summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--config.toml63
-rw-r--r--content/_index.md7
-rw-r--r--content/post/a-new-site.md14
-rw-r--r--content/post/back-again.md14
-rw-r--r--content/post/cedit-and-paredit.md44
-rw-r--r--content/post/emacs-package-archive-statistics.md169
-rw-r--r--content/post/git-cloning-similar-repositories.md19
-rw-r--r--content/post/opening-projects-with-projectile.md80
-rw-r--r--content/post/repository-management-with-ghq.md75
-rw-r--r--content/post/self-hosted-git.md146
-rw-r--r--layouts/index.html37
-rw-r--r--layouts/partials/hook_head_end.html2
-rw-r--r--netlify.toml77
-rw-r--r--static/.well-known/keybase.txt56
-rw-r--r--static/css/syntax.css81
-rw-r--r--static/img/me-large.jpgbin0 -> 185975 bytes
-rw-r--r--static/img/me-thumb.jpgbin0 -> 13898 bytes
-rw-r--r--static/keybase.txt55
-rw-r--r--static/public_key.asc16
-rw-r--r--static/robots.txt7
-rw-r--r--static/talks/fp-js/index.html431
-rw-r--r--static/talks/fp-js/index.org226
-rw-r--r--static/talks/fp-js/s5-blank.html50
-rw-r--r--static/talks/fp-js/ui/default/blank.gifbin0 -> 49 bytes
-rwxr-xr-xstatic/talks/fp-js/ui/default/bodybg.gifbin0 -> 10119 bytes
-rw-r--r--static/talks/fp-js/ui/default/framing.css23
-rw-r--r--static/talks/fp-js/ui/default/iepngfix.htc42
-rw-r--r--static/talks/fp-js/ui/default/opera.css7
-rw-r--r--static/talks/fp-js/ui/default/outline.css15
-rw-r--r--static/talks/fp-js/ui/default/pretty.css86
-rw-r--r--static/talks/fp-js/ui/default/print.css1
-rw-r--r--static/talks/fp-js/ui/default/s5-core.css9
-rw-r--r--static/talks/fp-js/ui/default/slides.css3
-rw-r--r--static/talks/fp-js/ui/default/slides.js553
-rw-r--r--themes/xmin/.gitignore5
-rw-r--r--themes/xmin/LICENSE.md (renamed from LICENSE.md)0
-rw-r--r--themes/xmin/README.md (renamed from README.md)0
-rw-r--r--themes/xmin/archetypes/default.md (renamed from archetypes/default.md)0
-rw-r--r--themes/xmin/exampleSite/config.toml (renamed from exampleSite/config.toml)0
-rw-r--r--themes/xmin/exampleSite/content/_index.Rmarkdown (renamed from exampleSite/content/_index.Rmarkdown)0
-rw-r--r--themes/xmin/exampleSite/content/_index.markdown (renamed from exampleSite/content/_index.markdown)0
-rw-r--r--themes/xmin/exampleSite/content/about.md (renamed from exampleSite/content/about.md)0
-rw-r--r--themes/xmin/exampleSite/content/note/2017-06-13-a-quick-note.md (renamed from exampleSite/content/note/2017-06-13-a-quick-note.md)0
-rw-r--r--themes/xmin/exampleSite/content/note/2017-06-14-another-note.md (renamed from exampleSite/content/note/2017-06-14-another-note.md)0
-rw-r--r--themes/xmin/exampleSite/content/post/2015-07-23-lorem-ipsum.md (renamed from exampleSite/content/post/2015-07-23-lorem-ipsum.md)0
-rw-r--r--themes/xmin/exampleSite/content/post/2016-02-14-hello-markdown.md (renamed from exampleSite/content/post/2016-02-14-hello-markdown.md)0
-rw-r--r--themes/xmin/exampleSite/layouts/partials/foot_custom.html (renamed from exampleSite/layouts/partials/foot_custom.html)0
-rw-r--r--themes/xmin/hugo-xmin.Rproj (renamed from hugo-xmin.Rproj)0
-rw-r--r--themes/xmin/images/screenshot.png (renamed from images/screenshot.png)bin37151 -> 37151 bytes
-rw-r--r--themes/xmin/images/tn.png (renamed from images/tn.png)bin21823 -> 21823 bytes
-rw-r--r--themes/xmin/layouts/404.html (renamed from layouts/404.html)0
-rw-r--r--themes/xmin/layouts/_default/list.html (renamed from layouts/_default/list.html)0
-rw-r--r--themes/xmin/layouts/_default/single.html (renamed from layouts/_default/single.html)0
-rw-r--r--themes/xmin/layouts/_default/terms.html (renamed from layouts/_default/terms.html)0
-rw-r--r--themes/xmin/layouts/partials/foot_custom.html (renamed from layouts/partials/foot_custom.html)0
-rw-r--r--themes/xmin/layouts/partials/footer.html (renamed from layouts/partials/footer.html)0
-rw-r--r--themes/xmin/layouts/partials/head_custom.html (renamed from layouts/partials/head_custom.html)0
-rw-r--r--themes/xmin/layouts/partials/header.html (renamed from layouts/partials/header.html)0
-rw-r--r--themes/xmin/static/css/fonts.css (renamed from static/css/fonts.css)0
-rw-r--r--themes/xmin/static/css/style.css (renamed from static/css/style.css)0
-rw-r--r--themes/xmin/theme.toml (renamed from theme.toml)0
62 files changed, 2417 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index ce130a0..a959665 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
-.Rproj.user
-.Rhistory
-.RData
-.Ruserdata
-exampleSite/public
+/public/
+
+# Local Netlify folder
+.netlify
\ No newline at end of file
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..de9e6a5
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,63 @@
+languageCode = "en-GB"
+baseurl = "https://www.alanpearce.eu"
+title = "Alan Pearce"
+theme = "xmin"
+pygmentsUseClasses = true
+pygmentsCodeFences = true
+
+[permalinks]
+    post = "/post/:slug"
+
+[Params]
+Description = "Developer, Emacser"
+footer = "Licensed under a <a rel=\"license\" href=\"http://creativecommons.org/licenses/by/4.0/\">Creative Commons Attribution 4.0 International License</a>."
+
+[Params.GPG]
+fingerprint = "48E6 576C 0707 388C B8BE FD0C CD4B EB92 A8D4 6583"
+url = "/public_key.asc"
+
+[author]
+    name = "Alan Pearce"
+    image = "/img/me-thumb.jpg"
+
+[[menu.main]]
+    name = "Home"
+    url = "/"
+    weight = 1
+[[menu.main]]
+    name = "Posts"
+    URL = "/post/"
+[[menu.main]]
+    name = "Repositories"
+    URL = "https://git.alanpearce.eu"
+
+[[menu.contact]]
+    name = "alan@alanpearce.eu"
+    URL = "mailto:alan@alanpearce.eu"
+    weight = 1
+[[menu.contact]]
+    name = "GitLab"
+    URL = "https://gitlab.com/alanpearce"
+[[menu.contact]]
+    name = "GitHub"
+    URL = "https://github.com/alanpearce"
+[[menu.contact]]
+    name = "StackOverflow Jobs"
+    url = "http://stackoverflow.com/users/story/381895"
+[[menu.contact]]
+    name = "Keybase"
+    url = "https://keybase.io/alanpearce"
+
+[privacy]
+  [privacy.disqus]
+    disable = true
+  [privacy.googleAnalytics]
+    disable = true
+  [privacy.instagram]
+    disable = true
+  [privacy.twitter]
+    disable = true
+  [privacy.vimeo]
+    disable = true
+  [privacy.youtube]
+    disable = true
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..2090377
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,7 @@
+---
+title: "Home"
+---
+## Hello
+Hi. My name is Alan, I live in Berlin, where I work as a
+Full-stack Developer.  I mostly write about Emacs and
+development-related topics.
diff --git a/content/post/a-new-site.md b/content/post/a-new-site.md
new file mode 100644
index 0000000..aa2efbf
--- /dev/null
+++ b/content/post/a-new-site.md
@@ -0,0 +1,14 @@
++++
+Categories = ["Geek"]
+Description = "I made a website."
+Title = "A New Site"
+Date = 2014-06-07T20:16:16Z
++++
+
+I finally got around to making a website.  I decided to use [Hugo][] with a slightly-modified [Hyde theme][]
+
+Someday I'll make my own theme, probably using [Stylus][] for CSS processing. But for now, this will do.  The more important thing is just to create some content.
+
+[Hugo]: http://hugo.spf13.com/
+[Hyde theme]: https://github.com/spf13/hyde
+[Stylus]: http://learnboost.github.io/stylus/
diff --git a/content/post/back-again.md b/content/post/back-again.md
new file mode 100644
index 0000000..7107a56
--- /dev/null
+++ b/content/post/back-again.md
@@ -0,0 +1,14 @@
++++
+Description = "I'm back"
+Tags = ["website"]
+date = "2017-05-06T16:55:57+02:00"
+title = "Back again"
++++
+
+I've not made any posts for quite some time.  My life has changed
+quite a bit, I've emigrated from the UK and it's only now that I'm
+starting to feel more settled.
+
+I hope to start posting a bit more often.  Hopefully this post,
+despite being light on content, will help me to get back into the
+sharing mindset.
diff --git a/content/post/cedit-and-paredit.md b/content/post/cedit-and-paredit.md
new file mode 100644
index 0000000..23fb1c4
--- /dev/null
+++ b/content/post/cedit-and-paredit.md
@@ -0,0 +1,44 @@
++++
+Categories = ["Emacs"]
+Description = "Cedit and paredit for structural editing"
+Tags = ["development", "emacs"]
+title = "Cedit and Paredit"
+date = 2014-08-04T07:10:14Z
++++
+
+I recently discovered [cedit][], which provides some structural
+commands for editing c-like languages.  (See this
+[Emacs Rocks! episode][e14] if you're not familiar with the concept:
+it introduces [paredit][], a structural editing mode for lisps).
+
+So, it deals with curly braces and semicolons, keeping things balanced
+and correct as show in its [screencast][cedit-readme]. It mentions that it
+integrates with [paredit][] rather than duplicating *all* its
+functionality.  After setting up cedit, I decided to try enabling
+paredit alongside cedit and disabling autopair.  Once I did,
+however, I noticed an annoying formatting issue: If I were to type
+`foo` and then `(`, paredit would format this as `foo ()`, which makes
+sense, considering that paredit is written for lisps — s-expressions
+are usually separated by spaces — but not so much for c-like languages.
+
+I was thinking about disabling paredit and going back to autopair,
+when I decided to look through the configuration variables for
+paredit.  Turns out it provides
+`paredit-space-for-delimiter-predicates`, which is a list of functions
+that control whether a space should be inserted.  So, solving the
+formatting issue turned out to be pretty simple:
+
+```elisp
+(defun ap/cedit-space-delimiter-p (endp delimiter)
+"Don't insert a space before delimiters in c-style modes"
+(not cedit-mode))
+(add-to-list 'paredit-space-for-delimiter-predicates #'ap/cedit-space-delimiter-p)
+```
+
+Hopefully that saves someone some time if they try to use the two
+together.
+
+[cedit]: https://github.com/zk-phi/cedit
+[cedit-readme]: https://github.com/zk-phi/cedit#readme
+[e14]: http://emacsrocks.com/e14.html
+[paredit]: http://www.emacswiki.org/emacs/ParEdit
diff --git a/content/post/emacs-package-archive-statistics.md b/content/post/emacs-package-archive-statistics.md
new file mode 100644
index 0000000..2275c9a
--- /dev/null
+++ b/content/post/emacs-package-archive-statistics.md
@@ -0,0 +1,169 @@
++++
+Categories = ["Emacs"]
+Description = "Working out which package archives I'm using"
+Tags = ["emacs"]
+title = "Emacs Package Archive Statistics"
+date = 2014-07-19T13:19:54Z
++++
+
+I use [cask][] for managing the dependencies of my Emacs
+configuration.  Whenever I opened my `Cask` file, I wondered if I
+really was using all the sources I had defined:
+
+```elisp
+(source gnu)
+(source marmalade)
+(source melpa)
+(source melpa-stable)
+(source org)
+```
+
+It seemed quite strange that we have so many package repositories in
+the Emacs world and I'm not even using all of them.  I find this state
+less than ideal, much as
+[Jorgen Schäfer details][state of emacs package archives].  My ideal
+package repository would be once that works with VCS releases, mostly
+because it's a much simpler process to work with than having to sign
+up to yet another website just to upload a package, then ensure it's
+kept up-to-date on every release.
+
+As such, I prefer the concepts behing [MELPA][] and [MELPA Stable][] to
+those of [Marmalade][].  [GNU ELPA][] doesn't appear to allow any
+submissions and [org][org archive] is specific to [org-mode].  I've
+also noticed that many packages I find and use are on github and so
+work with the [MELPA][] system.  However, I don't like [MELPA's][MELPA]
+versioning: it just gets the latest code and puts the build date in
+the version, meaning that packages could break at any time.
+
+So, ideally I would use [MELPA Stable][] as much as possible and reduce my
+usage of [Marmalade][] and [MELPA][].  [GNU ELPA][] doesn't appear to have
+many packages, but I wasn't sure if I was using any.
+I couldn't see the information listed in the `*Packages*` buffer, so I
+decided to try to figure out how to generate some usage statistics.
+
+I found [how to get a list of installed packages][], but that just gives
+a list:
+
+```elisp
+(ace-jump-mode ag auto-compile auto-indent-mode autopair ...)
+```
+
+I needed to get more information about those packages.  I looked at
+where `list-packages` gets that information from.  It seems that
+`package-archive-contents` is a list of cons cells:
+
+```elisp
+(org-plus-contrib .
+				  [(20140714)
+				  nil "Outline-based notes management and organizer" tar "org"])
+```
+
+Then created a function to loop over the contents of
+`package-activated-list`, retrieving the corresponding contents of
+`package-archive-contents`:
+
+```elisp
+(defun package-list-installed ()
+  (loop for pkg in package-activated-list
+        collect (assq pkg package-archive-contents)))
+```
+
+This generates a list of arrays from `package-archive-contents`.
+There are some helper functions in package.el such as
+`package-desc-kind`.  `package-desc-archive` was exactly what I
+needed.  I happened to be using a pretest version of Emacs at the time
+and didn't know that it's not in 24.3, so I just made sure it was defined:
+
+```elisp
+(if (not (fboundp #'package-desc-archive))
+    (defsubst package-desc-archive (desc)
+      (aref desc (1- (length desc)))))
+```
+
+Weirdly, some of the arrays (seemingly the ones from the
+[org archive][]) had a different length, but the repository/archive was
+always the last element, which is why I used `(1- (length ))` and not
+a constant, like the other `package-desc-*` functions.
+
+To generate a list of statistics, I just needed to loop over the
+installed packages from `package-list-installed` and update a count
+for each archive:
+
+```elisp
+(defun package-archive-stats ()
+  (let ((archives (makehash))
+        (assoc '()))
+    (dolist (arc package-archives)
+      (puthash (car arc) 0 archives))
+    (maphash (lambda (k v)
+               (setq assoc (cons (cons k v) assoc)))
+             (dolist (pkg (-filter #'identity (package-list-installed)) archives)
+               (let ((pkg-arc (package-desc-archive (cdr pkg))))
+                 (incf (gethash pkg-arc archives)))))
+    assoc))
+```
+
+Running this gives a list of cons cells:
+
+```elisp
+(("gnu" . 0)
+ ("org" . 1)
+ ("melpa-stable" . 2)
+ ("melpa" . 106)
+ ("marmalade" . 1))
+```
+
+I wrapped it in an interactive function so that I could check the
+numbers quickly:
+
+```elisp
+(defun package-show-archive-stats ()
+  (interactive)
+  (message "%s" (package-archive-stats)))
+```
+
+With that, I removed `(source gnu)` from my `Cask` file.  Now I had
+another question.  What package was installed from [marmalade][]?  In
+the lisp fashion, I created yet another function:
+
+```elisp
+(defun package-show-installed-from-archive (archive)
+  (interactive (list (helm-comp-read "Archive: " (mapcar #'car package-archives)
+                                      :must-match t)))
+  (let ((from-arc (mapcar #'car
+                          (--filter (equalp (package-desc-archive (cdr it)) archive)
+                                    (package-list-installed)))))
+    (if (called-interactively-p)
+        (message "%s" from-arc)
+      from-arc)))
+```
+(Non-helm users can replace `helm-comp-read` with
+`ido-completing-read` or similar)
+
+Running this with the argument `"marmalade"` gives:
+
+```elisp
+(php-extras)
+```
+
+I checked on [MELPA Stable][] and [MELPA][], but it's not available
+there.  Given that I use [php-extras][] quite a bit at work, I can't remove
+[marmalade][] just yet.  However, as it's a git repository, it should be
+easy for me to create a recipe for MELPA.  Then I can remove marmalade
+from my [cask][] configuration.  Hooray for simplification!
+
+Hopefully, packaging in Emacs will become simpler in the future.
+There are some interesting things in 24.4 like pinning packages to a
+repository, which would allow [MELPA Stable][] to be used even when
+[MELPA][] defines the same package with a higher "version".
+
+[cask]: https://github.com/cask/cask/
+[state of emacs package archives]: http://blog.jorgenschaefer.de/2014/06/the-sorry-state-of-emacs-lisp-package.html
+[marmalade]: http://marmalade-repo.org/
+[GNU ELPA]: http://elpa.gnu.org/packages/
+[MELPA]: http://hiddencameras.milkbox.net/
+[MELPA Stable]: http://melpa-stable.milkbox.net/
+[org archive]: http://orgmode.org/elpa.html
+[how to get a list of installed packages]: http://stackoverflow.com/questions/13866848/how-to-save-a-list-of-all-the-installed-packages-in-emacs-24
+[php-extras]: https://github.com/arnested/php-extras
+[org-mode]: http://orgmode.org/
diff --git a/content/post/git-cloning-similar-repositories.md b/content/post/git-cloning-similar-repositories.md
new file mode 100644
index 0000000..96ea7b4
--- /dev/null
+++ b/content/post/git-cloning-similar-repositories.md
@@ -0,0 +1,19 @@
++++
+Categories = ["Development"]
+Description = "Speed up cloning of similar git repositories"
+Tags = ["git"]
+title = "Cloning Similar Git Repositories"
+date = 2014-06-22T08:35:24Z
++++
+With multiple similar git repositories, for example where a base repository contains a framework or base system installation and other repositories are created from that repository, it's possible to save some time when cloning down another repository by using the `reference` option to [git-clone][]:
+
+	git clone git@github.com/my/repo --reference another-repo
+(Where `another-repo` points to a local version of a repository.)
+
+The reference here doesn't have to be the base repository itself, it could just be another variant of it.  The speedup can be quite dramatic if the repositories have megabytes of shared history, from minutes to seconds.
+
+On a related note, I'm surprised that [GitHub][] doesn't allow for multiple renamed forks, which would be very useful in this scenario.  [BitBucket][] does support this, however.  It even has a 'sync' button for pulling updates from the base into the child repositories, which is very useful, especially for those who prefer GUIs over CLIs.
+
+[git-clone]:https://www.kernel.org/pub/software/scm/git/docs/git-clone.html
+[GitHub]:https://github.com
+[BitBucket]:https://bitbucket.org/
\ No newline at end of file
diff --git a/content/post/opening-projects-with-projectile.md b/content/post/opening-projects-with-projectile.md
new file mode 100644
index 0000000..d88d309
--- /dev/null
+++ b/content/post/opening-projects-with-projectile.md
@@ -0,0 +1,80 @@
++++
+Categories = ["Emacs"]
+Description = ""
+Tags = ["emacs", "lisp"]
+title = "Opening Projects with Projectile"
+date = 2014-07-12T09:12:34Z
++++
+
+I use [Projectile][] for working with projects in Emacs.  It's really good at finding files in projects, working with source code indexes (I use [Global][]), and with its [perspective][] support, it's also great at separating projects into workspaces.  However, I've always felt it lacking in actually opening projects.  I tend to work on different projects all the time and `projectile-switch-project` only tracks projects once they've been opened initially (despite the name, it works across Emacs sessions).
+
+With this in mind, I decided to try to add support for opening projects under a given subdirectory, e.g. `~/projects`, regardless of whether or not I've visited them before.
+
+I saw that projectile uses [Dash.el][] in some places, and after reading about [anaphoric macros], I decided that I'd try to use them to aid me.
+
+```elisp
+(defun ap/subfolder-projects (dir)
+  (--map (file-relative-name it dir)
+         (-filter (lambda (subdir)
+                    (--reduce-from (or acc (funcall it subdir)) nil
+                                   projectile-project-root-files-functions))
+                  (-filter #'file-directory-p (directory-files dir t "\\<")))))
+```
+
+First, this filters the non-special files under `dir`, filtering non-directories.  Then it runs the list of `projectile-project-root-files-functions` on it to determine if it looks like a projectile project.  To make the list more readable, it makes the filenames relative to the passed-in directory.  It runs like this:
+                  
+```elisp
+(ap/subfolder-projects "~/projects") =>
+("dotfiles" "ggtags" …)
+```
+
+So, we've got ourselves a list, but now we need to be able to open the project that's there, even though the folders are relative.
+
+```elisp
+(defun ap/open-subfolder-project (from-dir &optional arg)
+  (let ((project-dir (projectile-completing-read "Open project: "
+                                     (ap/subfolder-projects from-dir))))
+    (projectile-switch-project-by-name (expand-file-name project-dir from-dir) arg)))
+```
+
+By wrapping the call to `ap/subfolder-projects` in another function that takes the same directory argument, we can re-use the project parent directory and expand the selected project name into an absolute path before passing it to `projectile-switch-project-by-name`.
+
+We get support for multiple completion systems for free, since projectile has a wrapper function that works with the default system, ido, [grizzl][] and recently, [helm][].
+
+Then I defined some helper functions to make it easy to open work and home projects.
+
+```elisp
+(defvar work-project-directory "~/work")
+(defvar home-project-directory "~/projects")
+
+(defun ap/open-work-project (&optional arg)
+  (interactive "P")
+  (ap/open-subfolder-project work-project-directory arg))
+
+(defun ap/open-home-project (&optional arg)
+  (interactive "P")
+  (ap/open-subfolder-project home-project-directory arg))
+```
+
+I could probably simplify this with a macro, but I'm not sure that there's much advantage in it.  I only have two project types right now, after all.
+
+With this all set up, whenever I want to start working on a project I just type `M-x home RET` to call up the list.
+
+I also considered trying to add all the projects under a directory to the projectile known project list.  I didn't find it quite as easy to use, but it's available below if anyone would prefer that style.
+
+```elisp
+(defun ap/-add-known-subfolder-projects (dir)
+  (-map #'projectile-add-known-project (--map (concat (file-name-as-directory dir) it) (ap/subfolder-projects dir))))
+
+(defun ap/add-known-subfolder-projects ()
+  (interactive)
+  (ap/-add-known-subfolder-projects (ido-read-directory-name "Add projects under: ")))
+```
+
+[Projectile]: https://github.com/bbatsov/projectile
+[Dash.el]: https://github.com/magnars/dash.el
+[Helm]: https://github.com/emacs-helm/helm
+[Global]: https://www.gnu.org/software/global/
+[Anaphoric macros]: https://en.wikipedia.org/wiki/Anaphoric_macro
+[Perspective]: https://github.com/nex3/perspective-el
+[Grizzl]: https://github.com/d11wtq/grizzl
diff --git a/content/post/repository-management-with-ghq.md b/content/post/repository-management-with-ghq.md
new file mode 100644
index 0000000..d831c9e
--- /dev/null
+++ b/content/post/repository-management-with-ghq.md
@@ -0,0 +1,75 @@
++++
+Tags = ["development","git"]
+date = "2017-05-06T23:31:51+02:00"
+title = "Repository management with ghq"
++++
+
+I recently encountered [ghq][], a tool for automatically organising VCS-backed
+projects automatically.  Give it a repository URL, it will clone a project to
+your projects dir (set by `$GHQ_ROOT`) like so:
+
+```sh
+$ ghq get https://github.com/motemen/ghq
+# Runs `git clone https://github.com/motemen/ghq ~/.ghq/github.com/motemen/ghq`
+```
+
+I don't like the idea of having projects hidden away, so I set
+`$GHQ_ROOT` to `$HOME/projects`.
+
+From there, the `list` and `look` subcommands allow listing
+repositories and visiting them in the shell (actually a subshell).
+
+I wanted a nicer way to visit project directories.  Since I'm
+using [fzf][] as a fuzzy-finder, I thought it would be nice to use it
+for this.  I created a simple function, `fp` (find project) to do that:
+
+```sh
+fp () {
+  ghq look $(ghq list | fzf +m)
+}
+```
+
+I ran into some issues with the subshell of `ghq look` and wondered
+whether it might be possible to create a zsh command to remove the
+need for a subshell.
+
+I found that `fzf` includes a [cd-widget function][fzf-cd-widget] and created
+something similar that uses `ghq` instead of `find`:
+
+```sh
+cd-project-widget () {
+  local cmd="ghq list"
+  setopt localoptions pipefail 2> /dev/null
+  local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" fzf +m)"
+  if [[ -z "$dir" ]]; then
+    zle redisplay
+    return 0
+  fi
+  cd $(ghq list --full-path | grep "$dir")
+  local ret=$?
+  zle reset-prompt
+  typeset -f zle-line-init >/dev/null && zle zle-line-init
+  return $ret
+}
+zle -N cd-project-widget
+```
+
+It should be quite simple to modify it to work with other
+fuzzy-finders.  The basic idea is to show the output of `ghq list` for
+selection, and use `ghq list --full-path` with the selected candidate
+to print the correct directory for `cd`.
+
+What's really nice about this, is that I can bind it to a key
+sequence:
+
+```sh
+bindkey '\es' cd-project-widget
+```
+
+Now I can press `M-s` in a shell, start typing "dotfiles" and press enter to `cd`
+to my [dotfiles][] project. Pretty neat!
+
+[ghq]:https://github.com/motemen/ghq
+[fzf]:https://github.com/junegunn/fzf
+[fzf-cd-widget]:https://github.com/junegunn/fzf/blob/337cdbb37c1efc49b09b4cacc6e9ee1369c7d76d/shell/key-bindings.zsh#L40-L54
+[dotfiles]:https://git.alanpearce.eu/dotfiles
diff --git a/content/post/self-hosted-git.md b/content/post/self-hosted-git.md
new file mode 100644
index 0000000..b26f61b
--- /dev/null
+++ b/content/post/self-hosted-git.md
@@ -0,0 +1,146 @@
++++
+Description = "I describe my git server setup (using cgit and gitolite), and what it allows"
+Tags = ["development","git"]
+date = "2017-06-04T12:33:02+02:00"
+title = "A simple, powerful self-hosted git setup"
++++
+
+I had been using [gogs][] for about a year.  It worked reasonably
+well, as it focuses on being a lightweight self-hosted GitHub
+replacement.  However, that wasn't really what I wanted.  I just
+wanted to host my own projects, I didn't need things like issues, pull
+requests or wikis.
+
+I recently switched to [gitolite][] and [cgit][], as they were even
+lighter on resources, don't require another login and work without
+an external database.  Gitolite is unusual in its configuration: it
+creates a git repository with its configuration file.  I will describe
+how I use them, rather than how to set them up, as they both have
+enough documentation on that.
+
+My gitolite configuration file looks like this:
+
+```
+repo gitolite-admin
+    RW+     =   alan
+
+repo dotfiles
+    C   =   alan
+    RW+ =   alan
+    R   =   READERS
+    option hook.post-update   =    github-mirror
+
+repo [a-z].*
+    C   =   alan
+    RW+ =   CREATOR
+    RW  =   WRITERS
+    R   =   READERS
+```
+
+The first block just allows me to work with the configuration
+repository, as the initial setup only enables one specific public SSH
+key, whereas I have three keys that I configure gitolite with.
+
+The second configures my dotfiles specifically.  Naturally, I should
+be the only person with read/write access.  The `R = READERS` line
+allows remote configuration of read permissions via `ssh $DOMAIN
+perms` (explained further below).  The last line runs a mirror script
+(just `git push --mirror…`) so that
+my [dotfiles repository on GitHub][dotfiles-github] is updated when I
+push to my private version.
+
+## Wild (or magic) repositories
+
+The third block is where things get interesting.  gitolite has a
+feature called [wildrepos][], which allows configuring a set of
+repositories at once, using a regular expression to match the
+repository name.
+
+The really nice thing here is that the repository need not exist
+before applying the configuration.  Therefore, the line `C = alan`
+means that I can create a remote repository automatically by cloning a
+repository URL that doesn't already exist.
+I can clone and create a new repo simultaneously like so:
+
+```shell
+cd ~/projects
+git clone alanpearce.eu:some-new-repository
+```
+
+But with [ghq][], which I [blogged about before][using-ghq], I don't
+have to concern myself with where to put the repository:
+
+```shell
+$ ghq get alanpearce.eu:some-new-repository
+     clone ssh://alanpearce.eu/some-new-repository -> /Volumes/Code/projects/alanpearce.eu/some-new-repository
+       git clone ssh://alanpearce.eu/some-new-repository /Volumes/Code/projects/alanpearce.eu/some-new-repository
+Cloning into '/Volumes/Code/projects/alanpearce.eu/some-new-repository'...
+Initialized empty Git repository in /var/lib/gitolite/repositories/some-new-repository.git/
+warning: You appear to have cloned an empty repository.
+```
+
+The nice URLs come from this piece of my SSH configuration:
+
+```
+Host alanpearce.eu
+  HostName git.alanpearce.eu
+  User gitolite
+```
+
+## Configuring wild repositories
+
+This repository would be private by default, but I can change that by an
+SSH command.  Here's how I would do it:
+
+```shell
+ssh alanpearce.eu perms some-new-repository + READERS gitweb
+ssh alanpearce.eu perms some-new-repository + READERS daemon
+```
+
+The first command makes it visible in cgit, whilst the second makes it
+clonable via `git://` url.  I can make a repository
+publically-clonable, but invisible on cgit by only allowing the `daemon`
+user and not `gitweb`, if I wanted.
+
+I can also add or change the description of a repository shown on cgit like
+so:
+
+```shell
+ssh alanpearce.eu desc some-new-repository 'A new repository'
+```
+
+All the remote commands exposed by gitolite are described in the
+`help` command e.g. `ssh alanpearce.eu help`
+
+```
+hello alan, this is gitolite@oak running gitolite3 (unknown) on git 2.12.2
+
+list of remote commands available:
+
+	D
+	desc
+	help
+	info
+	motd
+	perms
+	writable
+
+```
+
+## Conclusion
+
+I much prefer creating repositories in this way.  It's much simpler
+and allows me to get on with working on the repositories rather than
+going through a multi-step process in a web browser.
+
+With cgit and gitolite, I have a minimal setup, that does exactly what
+I want, without consuming many system resources with daemons.
+
+[gogs]:https://gogs.io/ "Go Git Service"
+[gitolite]:http://gitolite.com/gitolite/
+[cgit]:https://git.zx2c4.com/cgit/
+[NixOS]:http://nixos.org
+[dotfiles-github]:https://github.com/alanpearce/dotfiles
+[wildrepos]:http://gitolite.com/gitolite/wild/
+[ghq]:https://github.com/motemen/ghq
+[using-ghq]:{{< relref "/post/repository-management-with-ghq.md" >}} "Repository management with ghq"
diff --git a/layouts/index.html b/layouts/index.html
new file mode 100644
index 0000000..8aebc2d
--- /dev/null
+++ b/layouts/index.html
@@ -0,0 +1,37 @@
+{{ define "main" -}}
+<main>
+  <section>
+    {{ .Content }}
+  </section>
+  <section>
+    <h2>Latest Posts</h2>
+    <ul>
+      {{- range first 3 .Site.RegularPages }}
+      <li>
+        <a href="{{ .RelPermalink | strings.TrimRight "/" }}">{{ .Title }}</a>
+        <time datetime="{{ .Date.Format "2006-01-02T15:04:05Z" }}">{{ .Date.Format "Monday, 2 January 2006" }}</time>
+      </li>
+      {{- end }}
+    </ul>
+  </section>
+  <section>
+    <h2>Elsewhere on the Internet</h2>
+    <ul>
+      {{- range .Site.Menus.contact }}
+      <li>
+        {{- if hasPrefix .URL "mailto:" }}
+        <a href="{{ .URL }}" class="u-email email" rel="me">{{ .Name }}</a>
+        {{- else }}
+        <a href="{{ .URL }}" class="u-url url" rel="me">{{ .Name }}</a>
+        {{- end }}
+      </li>
+      {{- end }}
+    </ul>
+  </section>
+  <footer>
+    {{- with .Site.Params.GPG }}
+    GPG Key: <a href="{{ .url }}" rel="pgpkey">{{ .fingerprint }}</a>
+    {{- end }}
+  </footer>
+</main>
+{{- end }}
diff --git a/layouts/partials/hook_head_end.html b/layouts/partials/hook_head_end.html
new file mode 100644
index 0000000..13e0ec6
--- /dev/null
+++ b/layouts/partials/hook_head_end.html
@@ -0,0 +1,2 @@
+<link href="{{ if .IsPage }}{{ .Permalink | replaceRE "/$" "" }}{{ else }}{{ .Permalink }}{{ end }}" rel="canonical">
+<meta name="googlebot" content="noindex">
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..4a2efbb
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,77 @@
+[build]
+  publish = "public"
+  command = "hugo"
+  [build.environment]
+    HUGO_VERSION = "0.58.0"
+
+[context.production.environment]
+  HUGO_ENV = "production"
+
+[context.branch-deploy.environment]
+  HUGO_ENABLEGITINFO = "true"
+
+[[redirects]]
+  from = "https://alanpearceeu.netlify.com/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "https://www.alanpearce.co.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "https://alanpearce.co.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "http://www.alanpearce.co.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "http://alanpearce.co.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "https://www.alanpearce.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "https://alanpearce.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "http://www.alanpearce.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[redirects]]
+  from = "http://alanpearce.uk/*"
+  to = "https://www.alanpearce.eu/:splat"
+  status = 301
+  force = true
+
+[[headers]]
+  for = "/*"
+  [headers.values]
+    Link = '''
+</css/style.css>; rel=preload; as=stylesheet'''
+
+[[headers]]
+  for = "/post/*"
+  [headers.values]
+    Link = '''
+</css/style.css>; rel=preload; as=stylesheet'''
\ No newline at end of file
diff --git a/static/.well-known/keybase.txt b/static/.well-known/keybase.txt
new file mode 100644
index 0000000..f027aa9
--- /dev/null
+++ b/static/.well-known/keybase.txt
@@ -0,0 +1,56 @@
+==================================================================
+https://keybase.io/alanpearce
+--------------------------------------------------------------------
+
+I hereby claim:
+
+  * I am an admin of https://www.alanpearce.eu
+  * I am alanpearce (https://keybase.io/alanpearce) on keybase.
+  * I have a public key ASAktAZWh67GLebI8PNw4QNlJ4zEiIogKiQQ8WsqVsQa8Qo
+
+To do so, I am signing this object:
+
+{
+  "body": {
+    "key": {
+      "eldest_kid": "01200d892fbb34517abd5120fa546cb65dad1172cd85405cfae4936dcdb8bf5ac1850a",
+      "host": "keybase.io",
+      "kid": "012024b4065687aec62de6c8f0f370e10365278cc4888a202a2410f16b2a56c41af10a",
+      "uid": "91ae6da6b67277c6eded2451d6925919",
+      "username": "alanpearce"
+    },
+    "merkle_root": {
+      "ctime": 1529691082,
+      "hash": "6588b60bdcbf5836c74db6647f69ed9f88e8d45b237f896e75d790534fcb3058a3c2e3e9b7f026469b0ca30fe58f20e47fbe074306e02eba912348f19ab1abd2",
+      "hash_meta": "68aec5954816532401f402af55121c0c7496f3aac93475db68ea50e38e7e45b4",
+      "seqno": 3127786
+    },
+    "service": {
+      "entropy": "pkn3peHHXkyLn2KYC2q0CKkC",
+      "hostname": "www.alanpearce.eu",
+      "protocol": "https:"
+    },
+    "type": "web_service_binding",
+    "version": 2
+  },
+  "client": {
+    "name": "keybase.io go client",
+    "version": "2.1.1"
+  },
+  "ctime": 1529691093,
+  "expire_in": 504576000,
+  "prev": "9d580c5bd9f3b4a01356f507d808de55add562ddf3fded05a7d74299c5766503",
+  "seqno": 25,
+  "tag": "signature"
+}
+
+which yields the signature:
+
+hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgJLQGVoeuxi3myPDzcOEDZSeMxIiKICokEPFrKlbEGvEKp3BheWxvYWTESpcCGcQgnVgMW9nztKATVvUH2AjeVa3VYt3z/e0Fp9dCmcV2ZQPEIIRjxssrSyS8RF3Xr7Br780Q1Y0vy58txz8S6XBBaYpCAgHCo3NpZ8RAeAwN++lz+C+csgxZXXLSv76w2WcYaH41EcagALVLrULinV0j+Ea1TOUhmBfI9KNKFkOSiuEm+kOVktdf4BrWA6hzaWdfdHlwZSCkaGFzaIKkdHlwZQildmFsdWXEIFnViSN5iA8WlkQfuHAD3PEQ0gkZyjv59iuNx7EoBhrRo3RhZ80CAqd2ZXJzaW9uAQ==
+
+And finally, I am proving ownership of this host by posting or
+appending to this document.
+
+View my publicly-auditable identity here: https://keybase.io/alanpearce
+
+==================================================================
\ No newline at end of file
diff --git a/static/css/syntax.css b/static/css/syntax.css
new file mode 100644
index 0000000..23a21be
--- /dev/null
+++ b/static/css/syntax.css
@@ -0,0 +1,81 @@
+/* Background */ .chroma { background-color: #f8f8f8 }
+/* Other */ .chroma .x { color: #000000 }
+/* Error */ .chroma .err { color: #a40000 }
+/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
+/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
+/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
+/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; display: block; }
+/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; }
+/* Keyword */ .chroma .k { color: #204a87; font-weight: bold }
+/* KeywordConstant */ .chroma .kc { color: #204a87; font-weight: bold }
+/* KeywordDeclaration */ .chroma .kd { color: #204a87; font-weight: bold }
+/* KeywordNamespace */ .chroma .kn { color: #204a87; font-weight: bold }
+/* KeywordPseudo */ .chroma .kp { color: #204a87; font-weight: bold }
+/* KeywordReserved */ .chroma .kr { color: #204a87; font-weight: bold }
+/* KeywordType */ .chroma .kt { color: #204a87; font-weight: bold }
+/* Name */ .chroma .n { color: #000000 }
+/* NameAttribute */ .chroma .na { color: #c4a000 }
+/* NameBuiltin */ .chroma .nb { color: #204a87 }
+/* NameBuiltinPseudo */ .chroma .bp { color: #3465a4 }
+/* NameClass */ .chroma .nc { color: #000000 }
+/* NameConstant */ .chroma .no { color: #000000 }
+/* NameDecorator */ .chroma .nd { color: #5c35cc; font-weight: bold }
+/* NameEntity */ .chroma .ni { color: #ce5c00 }
+/* NameException */ .chroma .ne { color: #cc0000; font-weight: bold }
+/* NameFunction */ .chroma .nf { color: #000000 }
+/* NameFunctionMagic */ .chroma .fm { color: #000000 }
+/* NameLabel */ .chroma .nl { color: #f57900 }
+/* NameNamespace */ .chroma .nn { color: #000000 }
+/* NameOther */ .chroma .nx { color: #000000 }
+/* NameProperty */ .chroma .py { color: #000000 }
+/* NameTag */ .chroma .nt { color: #204a87; font-weight: bold }
+/* NameVariable */ .chroma .nv { color: #000000 }
+/* NameVariableClass */ .chroma .vc { color: #000000 }
+/* NameVariableGlobal */ .chroma .vg { color: #000000 }
+/* NameVariableInstance */ .chroma .vi { color: #000000 }
+/* NameVariableMagic */ .chroma .vm { color: #000000 }
+/* Literal */ .chroma .l { color: #000000 }
+/* LiteralDate */ .chroma .ld { color: #000000 }
+/* LiteralString */ .chroma .s { color: #4e9a06 }
+/* LiteralStringAffix */ .chroma .sa { color: #4e9a06 }
+/* LiteralStringBacktick */ .chroma .sb { color: #4e9a06 }
+/* LiteralStringChar */ .chroma .sc { color: #4e9a06 }
+/* LiteralStringDelimiter */ .chroma .dl { color: #4e9a06 }
+/* LiteralStringDoc */ .chroma .sd { color: #8f5902; font-style: italic }
+/* LiteralStringDouble */ .chroma .s2 { color: #4e9a06 }
+/* LiteralStringEscape */ .chroma .se { color: #4e9a06 }
+/* LiteralStringHeredoc */ .chroma .sh { color: #4e9a06 }
+/* LiteralStringInterpol */ .chroma .si { color: #4e9a06 }
+/* LiteralStringOther */ .chroma .sx { color: #4e9a06 }
+/* LiteralStringRegex */ .chroma .sr { color: #4e9a06 }
+/* LiteralStringSingle */ .chroma .s1 { color: #4e9a06 }
+/* LiteralStringSymbol */ .chroma .ss { color: #4e9a06 }
+/* LiteralNumber */ .chroma .m { color: #0000cf; font-weight: bold }
+/* LiteralNumberBin */ .chroma .mb { color: #0000cf; font-weight: bold }
+/* LiteralNumberFloat */ .chroma .mf { color: #0000cf; font-weight: bold }
+/* LiteralNumberHex */ .chroma .mh { color: #0000cf; font-weight: bold }
+/* LiteralNumberInteger */ .chroma .mi { color: #0000cf; font-weight: bold }
+/* LiteralNumberIntegerLong */ .chroma .il { color: #0000cf; font-weight: bold }
+/* LiteralNumberOct */ .chroma .mo { color: #0000cf; font-weight: bold }
+/* Operator */ .chroma .o { color: #ce5c00; font-weight: bold }
+/* OperatorWord */ .chroma .ow { color: #204a87; font-weight: bold }
+/* Punctuation */ .chroma .p { color: #000000; font-weight: bold }
+/* Comment */ .chroma .c { color: #8f5902; font-style: italic }
+/* CommentHashbang */ .chroma .ch { color: #8f5902; font-style: italic }
+/* CommentMultiline */ .chroma .cm { color: #8f5902; font-style: italic }
+/* CommentSingle */ .chroma .c1 { color: #8f5902; font-style: italic }
+/* CommentSpecial */ .chroma .cs { color: #8f5902; font-style: italic }
+/* CommentPreproc */ .chroma .cp { color: #8f5902; font-style: italic }
+/* CommentPreprocFile */ .chroma .cpf { color: #8f5902; font-style: italic }
+/* Generic */ .chroma .g { color: #000000 }
+/* GenericDeleted */ .chroma .gd { color: #a40000 }
+/* GenericEmph */ .chroma .ge { color: #000000; font-style: italic }
+/* GenericError */ .chroma .gr { color: #ef2929 }
+/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
+/* GenericInserted */ .chroma .gi { color: #00a000 }
+/* GenericOutput */ .chroma .go { color: #000000; font-style: italic }
+/* GenericPrompt */ .chroma .gp { color: #8f5902 }
+/* GenericStrong */ .chroma .gs { color: #000000; font-weight: bold }
+/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
+/* GenericTraceback */ .chroma .gt { color: #a40000; font-weight: bold }
+/* TextWhitespace */ .chroma .w { color: #f8f8f8 }
diff --git a/static/img/me-large.jpg b/static/img/me-large.jpg
new file mode 100644
index 0000000..1a1b369
--- /dev/null
+++ b/static/img/me-large.jpg
Binary files differdiff --git a/static/img/me-thumb.jpg b/static/img/me-thumb.jpg
new file mode 100644
index 0000000..d3c82f0
--- /dev/null
+++ b/static/img/me-thumb.jpg
Binary files differdiff --git a/static/keybase.txt b/static/keybase.txt
new file mode 100644
index 0000000..71b773e
--- /dev/null
+++ b/static/keybase.txt
@@ -0,0 +1,55 @@
+==================================================================
+https://keybase.io/alanpearce
+--------------------------------------------------------------------
+
+I hereby claim:
+
+  * I am an admin of https://alanpearce.uk
+  * I am alanpearce (https://keybase.io/alanpearce) on keybase.
+  * I have a public key ASANiS-7NFF6vVEg-lRstl2tEXLNhUBc-uSTbc24v1rBhQo
+
+To do so, I am signing this object:
+
+{
+  "body": {
+    "key": {
+      "eldest_kid": "01200d892fbb34517abd5120fa546cb65dad1172cd85405cfae4936dcdb8bf5ac1850a",
+      "host": "keybase.io",
+      "kid": "01200d892fbb34517abd5120fa546cb65dad1172cd85405cfae4936dcdb8bf5ac1850a",
+      "uid": "91ae6da6b67277c6eded2451d6925919",
+      "username": "alanpearce"
+    },
+    "merkle_root": {
+      "ctime": 1528123072,
+      "hash": "e0815741a6d5837c8c4a3af6900f5921484ebc1a0c8931284247134587f5cb2a330840662e835ad3d21397c5617cfa3c6152777b5b643821f5e80cd00d107af3",
+      "hash_meta": "c92918ad06b58621eea19be246eff63e3a86d00c619c5457ae41cc090bbcfd70",
+      "seqno": 2990872
+    },
+    "service": {
+      "hostname": "alanpearce.uk",
+      "protocol": "https:"
+    },
+    "type": "web_service_binding",
+    "version": 1
+  },
+  "client": {
+    "name": "keybase.io go client",
+    "version": "1.0.44"
+  },
+  "ctime": 1528123107,
+  "expire_in": 504576000,
+  "prev": "7c22548954f852d481dec3c2f6ccefb4be4322e2cd51016e2beb4be359b65763",
+  "seqno": 6,
+  "tag": "signature"
+}
+
+which yields the signature:
+
+hKRib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgDYkvuzRRer1RIPpUbLZdrRFyzYVAXPrkk23NuL9awYUKp3BheWxvYWTFA0d7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwMGQ4OTJmYmIzNDUxN2FiZDUxMjBmYTU0NmNiNjVkYWQxMTcyY2Q4NTQwNWNmYWU0OTM2ZGNkYjhiZjVhYzE4NTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwMGQ4OTJmYmIzNDUxN2FiZDUxMjBmYTU0NmNiNjVkYWQxMTcyY2Q4NTQwNWNmYWU0OTM2ZGNkYjhiZjVhYzE4NTBhIiwidWlkIjoiOTFhZTZkYTZiNjcyNzdjNmVkZWQyNDUxZDY5MjU5MTkiLCJ1c2VybmFtZSI6ImFsYW5wZWFyY2UifSwibWVya2xlX3Jvb3QiOnsiY3RpbWUiOjE1MjgxMjMwNzIsImhhc2giOiJlMDgxNTc0MWE2ZDU4MzdjOGM0YTNhZjY5MDBmNTkyMTQ4NGViYzFhMGM4OTMxMjg0MjQ3MTM0NTg3ZjVjYjJhMzMwODQwNjYyZTgzNWFkM2QyMTM5N2M1NjE3Y2ZhM2M2MTUyNzc3YjViNjQzODIxZjVlODBjZDAwZDEwN2FmMyIsImhhc2hfbWV0YSI6ImM5MjkxOGFkMDZiNTg2MjFlZWExOWJlMjQ2ZWZmNjNlM2E4NmQwMGM2MTljNTQ1N2FlNDFjYzA5MGJiY2ZkNzAiLCJzZXFubyI6Mjk5MDg3Mn0sInNlcnZpY2UiOnsiaG9zdG5hbWUiOiJhbGFucGVhcmNlLnVrIiwicHJvdG9jb2wiOiJodHRwczoifSwidHlwZSI6IndlYl9zZXJ2aWNlX2JpbmRpbmciLCJ2ZXJzaW9uIjoxfSwiY2xpZW50Ijp7Im5hbWUiOiJrZXliYXNlLmlvIGdvIGNsaWVudCIsInZlcnNpb24iOiIxLjAuNDQifSwiY3RpbWUiOjE1MjgxMjMxMDcsImV4cGlyZV9pbiI6NTA0NTc2MDAwLCJwcmV2IjoiN2MyMjU0ODk1NGY4NTJkNDgxZGVjM2MyZjZjY2VmYjRiZTQzMjJlMmNkNTEwMTZlMmJlYjRiZTM1OWI2NTc2MyIsInNlcW5vIjo2LCJ0YWciOiJzaWduYXR1cmUifaNzaWfEQLd80AcgXet5yGW0bL5y5IAf/rQ2R15NNRCk0T6qz/kQlf30JN810HcLoGrX3RcalgHbb8QbcgCWyd0kiep4CgWoc2lnX3R5cGUgpGhhc2iCpHR5cGUIpXZhbHVlxCB8OF/NBSFEq73lc0tGCM3gCNgr29QmOgZd9jvX68GNaKN0YWfNAgKndmVyc2lvbgE=
+
+And finally, I am proving ownership of this host by posting or
+appending to this document.
+
+View my publicly-auditable identity here: https://keybase.io/alanpearce
+
+==================================================================
diff --git a/static/public_key.asc b/static/public_key.asc
new file mode 100644
index 0000000..f081429
--- /dev/null
+++ b/static/public_key.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEXOZxBhYJKwYBBAHaRw8BAQdApEDmvmbv0fkrkND5LsR32g9QX8KtXAybgcCv
+euU6N9O0IEFsYW4gUGVhcmNlIDxhbGFuQGFsYW5wZWFyY2UuZXU+iIAEExYIABwF
+AlzmcQYCCwkCGwMEFQgJCgQWAgMBAheAAh4BABYJEM1L65Ko1GWDCxpUUkVaT1It
+R1BHgDABAICw5varaVWeuVlzJ0/XpLDFSHfY1CvQbMHe1LJ/iwGJAP9m3XC0yTyX
+uEG7w3R32Md5urcGH3fTIKK0ea6M+QVtArQgQWxhbiBQZWFyY2UgPGFsYW5Ac2F0
+b3NoaXBheS5pbz6IkAQTFggAOBYhBEjmV2wHBziMuL79DM1L65Ko1GWDBQJc5p5N
+AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEM1L65Ko1GWD2Z4A/jggQexr
+za4DXNK2jolKjIBL9S7pOGbXxldHo69HC+dLAP4lJlaExUbompFaXzV/FETH2pdQ
+azi51lmD8wN5YX4AA7g4BFzmcQYSCisGAQQBl1UBBQEBB0C5WCVOLRJpEHSWMVFH
+0xtWavMXh3QUaoNIrX0jcEtpIAMBCAeIbQQYFggACQUCXOZxBgIbDAAWCRDNS+uS
+qNRlgwsaVFJFWk9SLUdQR5PKAP93z83yYaOZMQKZYAD3h2LHdlKD2wl2LaLiFOll
+4N4ghgEA5iTNV6d5PHo8NV73T4xm97qY94LpF1cDWwBDYhb0ywI=
+=VSmX
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/static/robots.txt b/static/robots.txt
new file mode 100644
index 0000000..ef30e6f
--- /dev/null
+++ b/static/robots.txt
@@ -0,0 +1,7 @@
+User-agent: *
+Disallow:
+Host: alanpearce.eu
+Sitemap: https://alanpearce.eu/sitemap.xml
+
+User-agent: googlebot
+Disallow: /
diff --git a/static/talks/fp-js/index.html b/static/talks/fp-js/index.html
new file mode 100644
index 0000000..e90650c
--- /dev/null
+++ b/static/talks/fp-js/index.html
@@ -0,0 +1,431 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<!-- 2017-10-16 Mon 10:10 -->
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>Functional Programming in JavaScript</title>
+<meta name="generator" content="Org mode" />
+<meta name="author" content="Alan Pearce" />
+<meta name="version" content="S5 1.2a2" />
+<meta name='defaultView' content='slideshow' />
+<meta name='controlVis' content='hidden' />
+<!-- style sheet links -->
+<link rel='stylesheet' href='ui/default/outline.css' type='text/css' media='screen' id='outlineStyle' />
+<link rel='stylesheet' href='ui/default/print.css' type='text/css' media='print' id='slidePrint' />
+<link rel='stylesheet' href='ui/default/opera.css' type='text/css' media='projection' id='operaFix' />
+<link rel='stylesheet' href='ui/default/slides.css' type='text/css' media='screen' id='slideProj' />
+<!-- S5 JS -->
+<script src='ui/default/slides.js' type='text/javascript'></script>
+
+
+</head>
+<body>
+<div class="layout">
+<div id="controls"><!-- no edit --></div>
+<div id="currentSlide"><!-- no edit --></div>
+<div id="header" class="status">
+&#x20;
+</div>
+
+<div id="footer" class="status">
+<h1>Alan Pearce - Functional Programming in JavaScript</h1>
+</div>
+
+</div>
+<div id="content" class="presentation">
+<div id='title-slide' class='slide'>
+<h1>Functional Programming in JavaScript</h1>
+<h2></h2>
+<h2>Alan Pearce</h2>
+<h3><a href="mailto:alan@alanpearce.eu">alan@alanpearce.eu</a></h3>
+<h4></h4>
+</div>
+<div id='table-of-contents' class='slide'>
+<h1>Table of Contents</h1>
+<div id="text-table-of-contents">
+<ul>
+<li>1. Why?</li>
+<li>2. Concepts</li>
+<li>3. Further concepts</li>
+<li>4. First-class functions</li>
+<li>5. Higher-order functions</li>
+<li>6. Higher-order functions (cont.)</li>
+<li>7. Pure functions</li>
+<li>8. Recursion</li>
+<li>9. Partial application</li>
+<li>10. Partial application (cont.)</li>
+<li>11. Currying</li>
+<li>12. Easy Currying</li>
+<li>13. Practical Currying</li>
+<li>14. Functional composition</li>
+<li>15. Functional composition (cont.)</li>
+<li>16. pipe</li>
+<li>17. Point-free programming</li>
+<li>18. Further Resources</li>
+</ul>
+</div>
+</div>
+
+<div id="outline-container-org05e10f9" class="outline-1  slide">
+<h1 id="org05e10f9"><span class="section-number-1">1</span> Why?</h1>
+<div class="outline-text-1" id="text-1">
+<p>
+Imperative programming is concerned with <b>how</b>
+</p>
+
+<p>
+Functional programming is concerned with <b>what</b>
+</p>
+</div>
+</div>
+
+<div id="outline-container-org14fa5d9" class="outline-1  slide">
+<h1 id="org14fa5d9"><span class="section-number-1">2</span> Concepts</h1>
+<div class="outline-text-1" id="text-2">
+<ul class="org-ul">
+<li>First-class Functions</li>
+<li>Higher-order Functions</li>
+<li>Recursion</li>
+<li>Pure functions</li>
+<li>Currying &amp; Partial Application</li>
+</ul>
+</div>
+</div>
+
+<div id="outline-container-orgd930436" class="outline-1  slide">
+<h1 id="orgd930436"><span class="section-number-1">3</span> Further concepts</h1>
+<div class="outline-text-1" id="text-3">
+<ul class="org-ul">
+<li>Lazy Evaluation</li>
+<li>Types &amp; Data Structures</li>
+<li>Category Theory</li>
+</ul>
+</div>
+</div>
+
+<div id="outline-container-orgbd39891" class="outline-1  slide">
+<h1 id="orgbd39891"><span class="section-number-1">4</span> First-class functions</h1>
+<div class="outline-text-1" id="text-4">
+<ul class="org-ul">
+<li>Are values</li>
+<li>Have no restriction on their use</li>
+<li>Enable the use of callback functions in JavaScript</li>
+</ul>
+
+<p>
+
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">var fn = function () {
+  return 2
+}
+</code></pre>
+</div>
+</div>
+</div>
+
+
+<div id="outline-container-org8d1ee8a" class="outline-1  slide">
+<h1 id="org8d1ee8a"><span class="section-number-1">5</span> Higher-order functions</h1>
+<div class="outline-text-1" id="text-5">
+<p>
+Functions that operate on other functions are higher-order functions
+</p>
+
+<p>
+
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">var succ = function (x) {
+  return x + 1
+}
+
+var arr = [1, 2, 3, 4]
+
+arr.map(succ)
+</code></pre>
+</div>
+
+<p>
+Here, <code>Array.prototype.map</code> is the higher-order function
+</p>
+</div>
+</div>
+
+<div id="outline-container-org3bbf4ac" class="outline-1  slide">
+<h1 id="org3bbf4ac"><span class="section-number-1">6</span> Higher-order functions (cont.)</h1>
+<div class="outline-text-1" id="text-6">
+<p>
+Functions that return functions are also higher-order functions
+</p>
+
+<p>
+
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">function <span style="font-weight: bold;">adder</span> (n) {
+  return function (x) {
+    return n + x
+  }
+}
+
+var add1 = adder(1)
+</code></pre>
+</div>
+
+<p>
+<code>adder</code> is a higher-order function
+</p>
+</div>
+</div>
+
+<div id="outline-container-orgfb6eee2" class="outline-1  slide">
+<h1 id="orgfb6eee2"><span class="section-number-1">7</span> Pure functions</h1>
+<div class="outline-text-1" id="text-7">
+<p>
+Functions without side-effects
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">var succ = (x) =&gt; x + 1
+
+console.log(succ(succ(1)))
+
+<span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">could be optimised away by a compiler, e.g.:</span>
+
+console.log(3)
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-org16e9966" class="outline-1  slide">
+<h1 id="org16e9966"><span class="section-number-1">8</span> Recursion</h1>
+<div class="outline-text-1" id="text-8">
+<p>
+Functions that call themselves
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">function <span style="font-weight: bold;">fibonacci</span> (n) {
+  switch (n) {
+    case 0:
+    case 1:
+      return 1
+    default: 
+      return fibonacci(n - 1) + fibonacci(n - 2)
+  }
+}
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-org1b7b0af" class="outline-1  slide">
+<h1 id="org1b7b0af"><span class="section-number-1">9</span> Partial application</h1>
+<div class="outline-text-1" id="text-9">
+<p>
+The infamous <code>Function.prototype.bind</code> in JavaScript
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">function <span style="font-weight: bold;">add</span> (x, y) {
+  return x + y
+}
+
+var add1 = add.bind(add, 1)
+
+add1(3) <span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">= 4</span>
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-org796e6d3" class="outline-1  slide">
+<h1 id="org796e6d3"><span class="section-number-1">10</span> Partial application (cont.)</h1>
+<div class="outline-text-1" id="text-10">
+<p>
+After ES6 introduced arrow functions, partial application has become
+more popular
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">var add = x =&gt; y =&gt; x + y
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-orgeb4d67b" class="outline-1  slide">
+<h1 id="orgeb4d67b"><span class="section-number-1">11</span> Currying</h1>
+<div class="outline-text-1" id="text-11">
+<p>
+Related to partial application, but more implicit and general
+</p>
+
+<p>
+Translates <b><i>1</i> function of arity <i>n</i></b> to <b><i>n</i> functions of arity <i>1</i></b>
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">function <span style="font-weight: bold;">volume</span> (w, d, h) {
+  return w * d * h
+}
+
+var vol = curry(volume)
+vol(10)(20)(30)
+<span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">is strictly equivalent to</span>
+volume(10, 20, 30)
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-orgdfd8353" class="outline-1  slide">
+<h1 id="orgdfd8353"><span class="section-number-1">12</span> Easy Currying</h1>
+<div class="outline-text-1" id="text-12">
+<p>
+In order to make currying (and partial application) easier to use,
+move the <b>most important</b> argument to a function to the end:
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">var badMap = (arr, fn) =&gt; arr.map(fn)
+var goodMap = (fn, arr) =&gt; arr.map(fn)
+var curriedBadMap = curry(badmap)
+var curriedGoodMap = curry(goodMap)
+
+var goodDoubleArray = goodMap(x =&gt; x * 2)
+var badDoubleArray = badMap(_, x =&gt; x * 2)
+</code></pre>
+</div>
+
+<p>
+The bad version requires the curry function to support a magic
+placeholder argument and doesn't look as clean.
+</p>
+</div>
+</div>
+
+<div id="outline-container-org30accd5" class="outline-1  slide">
+<h1 id="org30accd5"><span class="section-number-1">13</span> Practical Currying</h1>
+<div class="outline-text-1" id="text-13">
+<p>
+Currying is not automatic in JavaScript, as in other languages
+</p>
+
+<p>
+External tools aren't (so far) able to statically analyse curried
+functions
+</p>
+
+<p>
+Solution: Don't expose curried functions
+Instead, write functions as if currying were automatic
+</p>
+
+<p>
+If consumers want to curry, they can.  If they don't, their editor or
+language server will show them the arguments
+</p>
+</div>
+</div>
+
+<div id="outline-container-org5f352dc" class="outline-1  slide">
+<h1 id="org5f352dc"><span class="section-number-1">14</span> Functional composition</h1>
+<div class="outline-text-1" id="text-14">
+<p>
+Creating functions from other functions
+</p>
+
+<p>
+Usually provided by <code>compose</code> (right-to-left) and <code>pipe</code> (left-to-right)
+</p>
+
+<p>
+A very simple definition of <code>compose</code> for only two functions would look like this
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">function <span style="font-weight: bold;">compose</span> (f, g) {
+  return function (...args) {
+    return f(g(...args))
+  }
+}
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-org0c2c4f9" class="outline-1  slide">
+<h1 id="org0c2c4f9"><span class="section-number-1">15</span> Functional composition (cont.)</h1>
+<div class="outline-text-1" id="text-15">
+<div class="org-src-container">
+<pre><code class="src src-js">var plusOne = x =&gt; x + 1
+var timesTwo = x =&gt; x * 2
+
+var plusOneTimesTwo = compose(timesTwo, plusOne)
+var timesTwoPlusOne = compose(plusOne, timesTwo)
+
+nextDoubled(3) <span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">= (3 + 1) * 2 = 8</span>
+doubledPlusOne(3) <span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">// </span><span style="color: #b8b8b8; background-color: #f8f8f8; font-style: italic;">= (3 * 2) + 1 = 7</span>
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-orgddc61f6" class="outline-1  slide">
+<h1 id="orgddc61f6"><span class="section-number-1">16</span> pipe</h1>
+<div class="outline-text-1" id="text-16">
+<p>
+What about <code>pipe</code>?
+</p>
+
+<p>
+<code>pipe</code> does the same thing, but runs the functions the other way around
+</p>
+
+<p>
+<code>pipe(f, g)</code> is the same as <code>compose(g, f)</code>
+</p>
+</div>
+</div>
+
+<div id="outline-container-org7426c58" class="outline-1  slide">
+<h1 id="org7426c58"><span class="section-number-1">17</span> Point-free programming</h1>
+<div class="outline-text-1" id="text-17">
+<p>
+With currying and higher-order functions, we (often) don't need to declare function arguments
+</p>
+
+<div class="org-src-container">
+<pre><code class="src src-js">var modulo = a =&gt; b =&gt; b % a
+var eq = a =&gt; b =&gt; a === b
+
+var isEven = x =&gt; eq(0)(modulo(2)(x))
+var isEvenPointFree = compose(eq(0), modulo(2))
+</code></pre>
+</div>
+</div>
+</div>
+
+<div id="outline-container-org8868cfb" class="outline-1  slide">
+<h1 id="org8868cfb"><span class="section-number-1">18</span> Further Resources</h1>
+<div class="outline-text-1" id="text-18">
+<ul class="org-ul">
+<li><a href="https://drboolean.gitbooks.io/mostly-adequate-guide/content/">Mostly adequate guide to FP (in javascript)</a></li>
+<li><a href="http://ramdajs.com/">Ramda</a>, a general-purpose FP library</li>
+<li><a href="https://sanctuary.js.org/">Sanctuary</a>, a JavaScript library for Haskellers</li>
+</ul>
+</div>
+</div>
+
+
+</div>
+</body>
+</html>
diff --git a/static/talks/fp-js/index.org b/static/talks/fp-js/index.org
new file mode 100644
index 0000000..8a4bf6c
--- /dev/null
+++ b/static/talks/fp-js/index.org
@@ -0,0 +1,226 @@
+#+TITLE: Functional Programming in JavaScript
+#+PROPERTY: :html-toplevel-hlevel 1
+#+PROPERTY: :with-toc 0
+* Why?
+
+Imperative programming is concerned with *how*
+
+Functional programming is concerned with *what*
+
+* Concepts
+
+ - First-class Functions
+ - Higher-order Functions
+ - Recursion
+ - Pure functions
+ - Currying & Partial Application
+
+* Further concepts
+
+ - Lazy Evaluation
+ - Types & Data Structures
+ - Category Theory
+
+* First-class functions
+
+ - Have no restriction on their use
+ - Are values
+ - Enable the use of callback functions in JavaScript
+
+
+
+#+BEGIN_SRC js
+const fn = function () {
+  return 2
+}
+#+END_SRC
+
+
+* Higher-order functions
+
+Functions that operate on other functions are higher-order functions
+
+
+
+#+BEGIN_SRC js
+const succ = function (x) {
+  return x + 1
+}
+
+const arr = [1, 2, 3, 4]
+
+arr.map(succ)
+#+END_SRC
+
+Here, =Array.prototype.map= is the higher-order function
+
+* Higher-order functions (cont.)
+
+Functions that return functions are also higher-order functions
+
+
+
+#+BEGIN_SRC js
+function adder (n) {
+  return function (x) {
+    return n + x
+  }
+}
+
+const add1 = adder(1)
+#+END_SRC
+
+=adder= is a higher-order function
+
+* Pure functions
+
+Functions without side-effects
+
+#+BEGIN_SRC js
+const succ = (x) => x + 1
+
+console.log(succ(succ(1)))
+
+// could be optimised away by a compiler, e.g.:
+
+console.log(3)
+#+END_SRC
+
+* Recursion
+
+Functions that call themselves
+
+#+BEGIN_SRC js
+function fibonacci (n) {
+  switch (n) {
+    case 0:
+    case 1:
+      return 1
+    default: 
+      return fibonacci(n - 1) + fibonacci(n - 2)
+  }
+}
+#+END_SRC
+
+* Partial application
+
+The infamous =Function.prototype.bind= in JavaScript
+
+#+BEGIN_SRC js
+function add (x, y) {
+  return x + y
+}
+
+const add1 = add.bind(add, 1)
+
+add1(3) // = 4
+#+END_SRC
+
+* Partial application (cont.)
+
+After ES6 introduced arrow functions, partial application has become
+more popular
+
+#+BEGIN_SRC js
+const add = x => y => x + y
+#+END_SRC
+
+* Currying
+
+Related to partial application, but more implicit and general
+
+Translates */1/ function of arity /n/* to */n/ functions of arity /1/*
+
+#+BEGIN_SRC js
+function volume (w, d, h) {
+  return w * d * h
+}
+
+const vol = curry(volume)
+vol(10)(20)(30)
+// is strictly equivalent to
+volume(10, 20, 30)
+#+END_SRC
+
+* Easy Currying
+
+In order to make currying (and partial application) easier to use,
+move the *most important* argument to a function to the end:
+
+#+BEGIN_SRC js
+const badMap = (arr, fn) => arr.map(fn)
+const goodMap = (fn, arr) => arr.map(fn)
+const curriedBadMap = curry(badmap)
+const curriedGoodMap = curry(goodMap)
+
+const goodDoubleArray = goodMap(x => x * 2)
+const badDoubleArray = badMap(_, x => x * 2)
+#+END_SRC
+
+The bad version requires the curry function to support a magic
+placeholder argument and doesn't look as clean.
+
+* Practical Currying
+
+Currying is not automatic in JavaScript, as in other languages
+
+External tools don't (currently) to statically analyse curried
+functions
+
+Solution: Don't expose curried functions
+Instead, write functions as if currying were automatic
+
+* Functional composition
+
+Creating functions from other functions
+
+Usually provided by =compose= (right-to-left) and =pipe= (left-to-right)
+
+A very simple definition of =compose= for only two functions would look like this
+
+#+BEGIN_SRC js
+function compose (f, g) {
+  return function (...args) {
+    return f(g(...args))
+  }
+}
+#+END_SRC
+
+* Functional composition (cont.)
+
+#+BEGIN_SRC js
+const plusOne = x => x + 1
+const timesTwo = x => x * 2
+
+const plusOneTimesTwo = compose(timesTwo, plusOne)
+const timesTwoPlusOne = compose(plusOne, timesTwo)
+
+nextDoubled(3) // = (3 + 1) * 2 = 8
+timesTwoPlusOne(3) // = (3 * 2) + 1 = 7
+#+END_SRC
+
+* pipe
+
+What about =pipe=?
+
+=pipe= does the same thing, but runs the functions the other way around
+
+=pipe(f, g)= is the same as =compose(g, f)=
+
+* Point-free programming
+
+With currying and higher-order functions, we (often) don't need to declare function arguments
+
+#+BEGIN_SRC js
+const modulo = a => b => b % a
+const eq = a => b => a === b
+
+const isEven = x => eq(0)(modulo(2)(x))
+const isEvenPointFree = compose(eq(0), modulo(2))
+#+END_SRC
+
+* Further Resources
+
+- [[https://drboolean.gitbooks.io/mostly-adequate-guide/content/][Mostly adequate guide to FP (in javascript)]]
+- [[http://ramdajs.com/][Ramda]], a general-purpose FP library
+- [[https://sanctuary.js.org/][Sanctuary]], a JavaScript library for Haskellers
diff --git a/static/talks/fp-js/s5-blank.html b/static/talks/fp-js/s5-blank.html
new file mode 100644
index 0000000..0d126c7
--- /dev/null
+++ b/static/talks/fp-js/s5-blank.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

+	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

+

+<html xmlns="http://www.w3.org/1999/xhtml">

+

+<head>

+<title>Functional Programming</title>

+<!-- metadata -->

+<meta name="generator" content="S5" />

+<meta name="version" content="S5 1.1" />

+<meta name="presdate" content="20050728" />

+<meta name="author" content="Eric A. Meyer" />

+<meta name="company" content="Complex Spiral Consulting" />

+<!-- configuration parameters -->

+<meta name="defaultView" content="slideshow" />

+<meta name="controlVis" content="hidden" />

+<!-- style sheet links -->

+<link rel="stylesheet" href="ui/default/slides.css" type="text/css" media="projection" id="slideProj" />

+<link rel="stylesheet" href="ui/default/outline.css" type="text/css" media="screen" id="outlineStyle" />

+<link rel="stylesheet" href="ui/default/print.css" type="text/css" media="print" id="slidePrint" />

+<link rel="stylesheet" href="ui/default/opera.css" type="text/css" media="projection" id="operaFix" />

+<!-- S5 JS -->

+<script src="ui/default/slides.js" type="text/javascript"></script>

+</head>

+<body>

+

+<div class="layout">

+<div id="controls"><!-- DO NOT EDIT --></div>

+<div id="currentSlide"><!-- DO NOT EDIT --></div>

+<div id="header"></div>

+<div id="footer">

+<h1>Functional Programming</h1>

+</div>

+

+</div>

+

+

+<div class="presentation">

+

+<div class="slide">

+<h1>Functional Programming</h1>

+<h2>in JavaScript</h2>

+<h3>Alan Pearce</h3>

+</div>

+

+

+</div>

+

+</body>

+</html>

diff --git a/static/talks/fp-js/ui/default/blank.gif b/static/talks/fp-js/ui/default/blank.gif
new file mode 100644
index 0000000..75b945d
--- /dev/null
+++ b/static/talks/fp-js/ui/default/blank.gif
Binary files differdiff --git a/static/talks/fp-js/ui/default/bodybg.gif b/static/talks/fp-js/ui/default/bodybg.gif
new file mode 100755
index 0000000..5f448a1
--- /dev/null
+++ b/static/talks/fp-js/ui/default/bodybg.gif
Binary files differdiff --git a/static/talks/fp-js/ui/default/framing.css b/static/talks/fp-js/ui/default/framing.css
new file mode 100644
index 0000000..14d8509
--- /dev/null
+++ b/static/talks/fp-js/ui/default/framing.css
@@ -0,0 +1,23 @@
+/* The following styles size, place, and layer the slide components.
+   Edit these if you want to change the overall slide layout.
+   The commented lines can be uncommented (and modified, if necessary) 
+    to help you with the rearrangement process. */
+
+/* target = 1024x768 */
+
+div#header, div#footer, .slide {width: 100%; top: 0; left: 0;}
+div#header {top: 0; height: 3em; z-index: 1;}
+div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;}
+.slide {top: 0; width: 92%; padding: 3.5em 4% 4%; z-index: 2;  list-style: none;}
+div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;}
+div#controls form {position: absolute; bottom: 0; right: 0; width: 100%;
+  margin: 0;}
+#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; z-index: 10;}
+html>body #currentSlide {position: fixed;}
+
+/*
+div#header {background: #FCC;}
+div#footer {background: #CCF;}
+div#controls {background: #BBD;}
+div#currentSlide {background: #FFC;}
+*/
diff --git a/static/talks/fp-js/ui/default/iepngfix.htc b/static/talks/fp-js/ui/default/iepngfix.htc
new file mode 100644
index 0000000..bba2db7
--- /dev/null
+++ b/static/talks/fp-js/ui/default/iepngfix.htc
@@ -0,0 +1,42 @@
+<public:component>

+<public:attach event="onpropertychange" onevent="doFix()" />

+

+<script>

+

+// IE5.5+ PNG Alpha Fix v1.0 by Angus Turnbull http://www.twinhelix.com

+// Free usage permitted as long as this notice remains intact.

+

+// This must be a path to a blank image. That's all the configuration you need here.

+var blankImg = 'ui/default/blank.gif';

+

+var f = 'DXImageTransform.Microsoft.AlphaImageLoader';

+

+function filt(s, m) {

+ if (filters[f]) {

+  filters[f].enabled = s ? true : false;

+  if (s) with (filters[f]) { src = s; sizingMethod = m }

+ } else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")';

+}

+

+function doFix() {

+ if ((parseFloat(navigator.userAgent.match(/MSIE (\S+)/)[1]) < 5.5) ||

+  (event && !/(background|src)/.test(event.propertyName))) return;

+

+ if (tagName == 'IMG') {

+  if ((/\.png$/i).test(src)) {

+   filt(src, 'image');  // was 'scale'

+   src = blankImg;

+  } else if (src.indexOf(blankImg) < 0) filt();

+ } else if (style.backgroundImage) {

+  if (style.backgroundImage.match(/^url[("']+(.*\.png)[)"']+$/i)) {

+   var s = RegExp.$1;

+   style.backgroundImage = '';

+   filt(s, 'crop');

+  } else filt();

+ }

+}

+

+doFix();

+

+</script>

+</public:component>
\ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/opera.css b/static/talks/fp-js/ui/default/opera.css
new file mode 100644
index 0000000..9e9d2a3
--- /dev/null
+++ b/static/talks/fp-js/ui/default/opera.css
@@ -0,0 +1,7 @@
+/* DO NOT CHANGE THESE unless you really want to break Opera Show */
+.slide {
+	visibility: visible !important;
+	position: static !important;
+	page-break-before: always;
+}
+#slide0 {page-break-before: avoid;}
diff --git a/static/talks/fp-js/ui/default/outline.css b/static/talks/fp-js/ui/default/outline.css
new file mode 100644
index 0000000..62db519
--- /dev/null
+++ b/static/talks/fp-js/ui/default/outline.css
@@ -0,0 +1,15 @@
+/* don't change this unless you want the layout stuff to show up in the outline view! */
+
+.layout div, #footer *, #controlForm * {display: none;}
+#footer, #controls, #controlForm, #navLinks, #toggle {
+  display: block; visibility: visible; margin: 0; padding: 0;}
+#toggle {float: right; padding: 0.5em;}
+html>body #toggle {position: fixed; top: 0; right: 0;}
+
+/* making the outline look pretty-ish */
+
+#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;}
+#slide0 h1 {padding-top: 1.5em;}
+.slide h1 {margin: 1.5em 0 0; padding-top: 0.25em;
+  border-top: 1px solid #888; border-bottom: 1px solid #AAA;}
+#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;}
diff --git a/static/talks/fp-js/ui/default/pretty.css b/static/talks/fp-js/ui/default/pretty.css
new file mode 100644
index 0000000..3d3acef
--- /dev/null
+++ b/static/talks/fp-js/ui/default/pretty.css
@@ -0,0 +1,86 @@
+/* Following are the presentation styles -- edit away! */
+
+body {background: #FFF url(bodybg.gif) -16px 0 no-repeat; color: #000; font-size: 2em;}
+:link, :visited {text-decoration: none; color: #00C;}
+#controls :active {color: #88A !important;}
+#controls :focus {outline: 1px dotted #227;}
+h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;}
+ul, pre {margin: 0; line-height: 1em;}
+html, body {margin: 0; padding: 0;}
+
+blockquote, q {font-style: italic;}
+blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em; text-align: center; font-size: 1em;}
+blockquote p {margin: 0;}
+blockquote i {font-style: normal;}
+blockquote b {display: block; margin-top: 0.5em; font-weight: normal; font-size: smaller; font-style: normal;}
+blockquote b i {font-style: italic;}
+
+kbd {font-weight: bold; font-size: 1em;}
+sup {font-size: smaller; line-height: 1px;}
+
+.slide code {padding: 2px 0.25em; font-weight: bold; color: #533;}
+.slide code.bad, code del {color: red;}
+.slide code.old {color: silver;}
+.slide pre {padding: 0; margin: 0.25em 0 0.5em 0.5em; color: #533; font-size: 90%;}
+.slide pre code {display: block;}
+.slide ul {margin-left: 5%; margin-right: 7%; list-style: disc;}
+.slide li {margin-top: 0.75em; margin-right: 0;}
+.slide ul ul {line-height: 1;}
+.slide ul ul li {margin: .2em; font-size: 85%; list-style: square;}
+.slide img.leader {display: block; margin: 0 auto;}
+
+div#header, div#footer {background: #005; color: #AAB;
+  font-family: Verdana, Helvetica, sans-serif;}
+div#header {background: #005 url(bodybg.gif) -16px 0 no-repeat;
+  line-height: 1px;}
+div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;}
+#footer h1, #footer h2 {display: block; padding: 0 1em;}
+#footer h2 {font-style: italic;}
+
+div.long {font-size: 0.75em;}
+.slide h1 {position: absolute; top: 0.7em; left: 87px; z-index: 1;
+  margin: 0; padding: 0.3em 0 0 50px; white-space: nowrap;
+  font: bold 150%/1em Helvetica, sans-serif; text-transform: capitalize;
+  color: #DDE; background: #005;}
+.slide h3 {font-size: 130%;}
+h1 abbr {font-variant: small-caps;}
+
+div#controls {position: absolute; left: 50%; bottom: 0;
+  width: 50%;
+  text-align: right; font: bold 0.9em Verdana, Helvetica, sans-serif;}
+html>body div#controls {position: fixed; padding: 0 0 1em 0;
+  top: auto;}
+div#controls form {position: absolute; bottom: 0; right: 0; width: 100%;
+  margin: 0; padding: 0;}
+#controls #navLinks a {padding: 0; margin: 0 0.5em; 
+  background: #005; border: none; color: #779; 
+  cursor: pointer;}
+#controls #navList {height: 1em;}
+#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; background: #DDD; color: #227;}
+
+#currentSlide {text-align: center; font-size: 0.5em; color: #449;}
+
+#slide0 {padding-top: 3.5em; font-size: 90%;}
+#slide0 h1 {position: static; margin: 1em 0 0; padding: 0;
+   font: bold 2em Helvetica, sans-serif; white-space: normal;
+   color: #000; background: transparent;}
+#slide0 h2 {font: bold italic 1em Helvetica, sans-serif; margin: 0.25em;}
+#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;}
+#slide0 h4 {margin-top: 0; font-size: 1em;}
+
+ul.urls {list-style: none; display: inline; margin: 0;}
+.urls li {display: inline; margin: 0;}
+.note {display: none;}
+.external {border-bottom: 1px dotted gray;}
+html>body .external {border-bottom: none;}
+.external:after {content: " \274F"; font-size: smaller; color: #77B;}
+
+.incremental, .incremental *, .incremental *:after {color: #DDE; visibility: visible;}
+img.incremental {visibility: hidden;}
+.slide .current {color: #B02;}
+
+
+/* diagnostics
+
+li:after {content: " [" attr(class) "]"; color: #F88;}
+ */
\ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/print.css b/static/talks/fp-js/ui/default/print.css
new file mode 100644
index 0000000..e7a71d1
--- /dev/null
+++ b/static/talks/fp-js/ui/default/print.css
@@ -0,0 +1 @@
+/* The following rule is necessary to have all slides appear in print! DO NOT REMOVE IT! */
.slide, ul {page-break-inside: avoid; visibility: visible !important;}
h1 {page-break-after: avoid;}

body {font-size: 12pt; background: white;}
* {color: black;}

#slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;}
#slide0 h3 {margin: 0; padding: 0;}
#slide0 h4 {margin: 0 0 0.5em; padding: 0;}
#slide0 {margin-bottom: 3em;}

h1 {border-top: 2pt solid gray; border-bottom: 1px dotted silver;}
.extra {background: transparent !important;}
div.extra, pre.extra, .example {font-size: 10pt; color: #333;}
ul.extra a {font-weight: bold;}
p.example {display: none;}

#header {display: none;}
#footer h1 {margin: 0; border-bottom: 1px solid; color: gray; font-style: italic;}
#footer h2, #controls {display: none;}

/* The following rule keeps the layout stuff out of print.  Remove at your own risk! */
.layout, .layout * {display: none !important;}
\ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/s5-core.css b/static/talks/fp-js/ui/default/s5-core.css
new file mode 100644
index 0000000..86444e0
--- /dev/null
+++ b/static/talks/fp-js/ui/default/s5-core.css
@@ -0,0 +1,9 @@
+/* Do not edit or override these styles! The system will likely break if you do. */
+
+div#header, div#footer, div#controls, .slide {position: absolute;}
+html>body div#header, html>body div#footer, 
+  html>body div#controls, html>body .slide {position: fixed;}
+.handout {display: none;}
+.layout {display: block;}
+.slide, .hideme, .incremental {visibility: hidden;}
+#slide0 {visibility: visible;}
diff --git a/static/talks/fp-js/ui/default/slides.css b/static/talks/fp-js/ui/default/slides.css
new file mode 100644
index 0000000..0786d7d
--- /dev/null
+++ b/static/talks/fp-js/ui/default/slides.css
@@ -0,0 +1,3 @@
+@import url(s5-core.css); /* required to make the slide show run at all */
+@import url(framing.css); /* sets basic placement and size of slide components */
+@import url(pretty.css);  /* stuff that makes the slides look better than blah */
\ No newline at end of file
diff --git a/static/talks/fp-js/ui/default/slides.js b/static/talks/fp-js/ui/default/slides.js
new file mode 100644
index 0000000..38fe853
--- /dev/null
+++ b/static/talks/fp-js/ui/default/slides.js
@@ -0,0 +1,553 @@
+// S5 v1.1 slides.js -- released into the Public Domain
+//
+// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for information 
+// about all the wonderful and talented contributors to this code!
+
+var undef;
+var slideCSS = '';
+var snum = 0;
+var smax = 1;
+var incpos = 0;
+var number = undef;
+var s5mode = true;
+var defaultView = 'slideshow';
+var controlVis = 'visible';
+
+var isIE = navigator.appName == 'Microsoft Internet Explorer' && navigator.userAgent.indexOf('Opera') < 1 ? 1 : 0;
+var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0;
+var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0;
+
+function hasClass(object, className) {
+	if (!object.className) return false;
+	return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1);
+}
+
+function hasValue(object, value) {
+	if (!object) return false;
+	return (object.search('(^|\\s)' + value + '(\\s|$)') != -1);
+}
+
+function removeClass(object,className) {
+	if (!object) return;
+	object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2);
+}
+
+function addClass(object,className) {
+	if (!object || hasClass(object, className)) return;
+	if (object.className) {
+		object.className += ' '+className;
+	} else {
+		object.className = className;
+	}
+}
+
+function GetElementsWithClassName(elementName,className) {
+	var allElements = document.getElementsByTagName(elementName);
+	var elemColl = new Array();
+	for (var i = 0; i< allElements.length; i++) {
+		if (hasClass(allElements[i], className)) {
+			elemColl[elemColl.length] = allElements[i];
+		}
+	}
+	return elemColl;
+}
+
+function isParentOrSelf(element, id) {
+	if (element == null || element.nodeName=='BODY') return false;
+	else if (element.id == id) return true;
+	else return isParentOrSelf(element.parentNode, id);
+}
+
+function nodeValue(node) {
+	var result = "";
+	if (node.nodeType == 1) {
+		var children = node.childNodes;
+		for (var i = 0; i < children.length; ++i) {
+			result += nodeValue(children[i]);
+		}		
+	}
+	else if (node.nodeType == 3) {
+		result = node.nodeValue;
+	}
+	return(result);
+}
+
+function slideLabel() {
+	var slideColl = GetElementsWithClassName('*','slide');
+	var list = document.getElementById('jumplist');
+	smax = slideColl.length;
+	for (var n = 0; n < smax; n++) {
+		var obj = slideColl[n];
+
+		var did = 'slide' + n.toString();
+		obj.setAttribute('id',did);
+		if (isOp) continue;
+
+		var otext = '';
+		var menu = obj.firstChild;
+		if (!menu) continue; // to cope with empty slides
+		while (menu && menu.nodeType == 3) {
+			menu = menu.nextSibling;
+		}
+	 	if (!menu) continue; // to cope with slides with only text nodes
+
+		var menunodes = menu.childNodes;
+		for (var o = 0; o < menunodes.length; o++) {
+			otext += nodeValue(menunodes[o]);
+		}
+		list.options[list.length] = new Option(n + ' : '  + otext, n);
+	}
+}
+
+function currentSlide() {
+	var cs;
+	if (document.getElementById) {
+		cs = document.getElementById('currentSlide');
+	} else {
+		cs = document.currentSlide;
+	}
+	cs.innerHTML = '<span id="csHere">' + snum + '<\/span> ' + 
+		'<span id="csSep">\/<\/span> ' + 
+		'<span id="csTotal">' + (smax-1) + '<\/span>';
+	if (snum == 0) {
+		cs.style.visibility = 'hidden';
+	} else {
+		cs.style.visibility = 'visible';
+	}
+}
+
+function go(step) {
+	if (document.getElementById('slideProj').disabled || step == 0) return;
+	var jl = document.getElementById('jumplist');
+	var cid = 'slide' + snum;
+	var ce = document.getElementById(cid);
+	if (incrementals[snum].length > 0) {
+		for (var i = 0; i < incrementals[snum].length; i++) {
+			removeClass(incrementals[snum][i], 'current');
+			removeClass(incrementals[snum][i], 'incremental');
+		}
+	}
+	if (step != 'j') {
+		snum += step;
+		lmax = smax - 1;
+		if (snum > lmax) snum = lmax;
+		if (snum < 0) snum = 0;
+	} else
+		snum = parseInt(jl.value);
+	var nid = 'slide' + snum;
+	var ne = document.getElementById(nid);
+	if (!ne) {
+		ne = document.getElementById('slide0');
+		snum = 0;
+	}
+	if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;}
+	if (incrementals[snum].length > 0 && incpos == 0) {
+		for (var i = 0; i < incrementals[snum].length; i++) {
+			if (hasClass(incrementals[snum][i], 'current'))
+				incpos = i + 1;
+			else
+				addClass(incrementals[snum][i], 'incremental');
+		}
+	}
+	if (incrementals[snum].length > 0 && incpos > 0)
+		addClass(incrementals[snum][incpos - 1], 'current');
+	ce.style.visibility = 'hidden';
+	ne.style.visibility = 'visible';
+	jl.selectedIndex = snum;
+	currentSlide();
+	number = 0;
+}
+
+function goTo(target) {
+	if (target >= smax || target == snum) return;
+	go(target - snum);
+}
+
+function subgo(step) {
+	if (step > 0) {
+		removeClass(incrementals[snum][incpos - 1],'current');
+		removeClass(incrementals[snum][incpos], 'incremental');
+		addClass(incrementals[snum][incpos],'current');
+		incpos++;
+	} else {
+		incpos--;
+		removeClass(incrementals[snum][incpos],'current');
+		addClass(incrementals[snum][incpos], 'incremental');
+		addClass(incrementals[snum][incpos - 1],'current');
+	}
+}
+
+function toggle() {
+	var slideColl = GetElementsWithClassName('*','slide');
+	var slides = document.getElementById('slideProj');
+	var outline = document.getElementById('outlineStyle');
+	if (!slides.disabled) {
+		slides.disabled = true;
+		outline.disabled = false;
+		s5mode = false;
+		fontSize('1em');
+		for (var n = 0; n < smax; n++) {
+			var slide = slideColl[n];
+			slide.style.visibility = 'visible';
+		}
+	} else {
+		slides.disabled = false;
+		outline.disabled = true;
+		s5mode = true;
+		fontScale();
+		for (var n = 0; n < smax; n++) {
+			var slide = slideColl[n];
+			slide.style.visibility = 'hidden';
+		}
+		slideColl[snum].style.visibility = 'visible';
+	}
+}
+
+function showHide(action) {
+	var obj = GetElementsWithClassName('*','hideme')[0];
+	switch (action) {
+	case 's': obj.style.visibility = 'visible'; break;
+	case 'h': obj.style.visibility = 'hidden'; break;
+	case 'k':
+		if (obj.style.visibility != 'visible') {
+			obj.style.visibility = 'visible';
+		} else {
+			obj.style.visibility = 'hidden';
+		}
+	break;
+	}
+}
+
+// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/)
+function keys(key) {
+	if (!key) {
+		key = event;
+		key.which = key.keyCode;
+	}
+	if (key.which == 84) {
+		toggle();
+		return;
+	}
+	if (s5mode) {
+		switch (key.which) {
+			case 10: // return
+			case 13: // enter
+				if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return;
+				if (key.target && isParentOrSelf(key.target, 'controls')) return;
+				if(number != undef) {
+					goTo(number);
+					break;
+				}
+			case 32: // spacebar
+			case 34: // page down
+			case 39: // rightkey
+			case 40: // downkey
+				if(number != undef) {
+					go(number);
+				} else if (!incrementals[snum] || incpos >= incrementals[snum].length) {
+					go(1);
+				} else {
+					subgo(1);
+				}
+				break;
+			case 33: // page up
+			case 37: // leftkey
+			case 38: // upkey
+				if(number != undef) {
+					go(-1 * number);
+				} else if (!incrementals[snum] || incpos <= 0) {
+					go(-1);
+				} else {
+					subgo(-1);
+				}
+				break;
+			case 36: // home
+				goTo(0);
+				break;
+			case 35: // end
+				goTo(smax-1);
+				break;
+			case 67: // c
+				showHide('k');
+				break;
+		}
+		if (key.which < 48 || key.which > 57) {
+			number = undef;
+		} else {
+			if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return;
+			if (key.target && isParentOrSelf(key.target, 'controls')) return;
+			number = (((number != undef) ? number : 0) * 10) + (key.which - 48);
+		}
+	}
+	return false;
+}
+
+function clicker(e) {
+	number = undef;
+	var target;
+	if (window.event) {
+		target = window.event.srcElement;
+		e = window.event;
+	} else target = e.target;
+	if (target.getAttribute('href') != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target,'object')) return true;
+	if (!e.which || e.which == 1) {
+		if (!incrementals[snum] || incpos >= incrementals[snum].length) {
+			go(1);
+		} else {
+			subgo(1);
+		}
+	}
+}
+
+function findSlide(hash) {
+	var target = null;
+	var slides = GetElementsWithClassName('*','slide');
+	for (var i = 0; i < slides.length; i++) {
+		var targetSlide = slides[i];
+		if ( (targetSlide.name && targetSlide.name == hash)
+		 || (targetSlide.id && targetSlide.id == hash) ) {
+			target = targetSlide;
+			break;
+		}
+	}
+	while(target != null && target.nodeName != 'BODY') {
+		if (hasClass(target, 'slide')) {
+			return parseInt(target.id.slice(5));
+		}
+		target = target.parentNode;
+	}
+	return null;
+}
+
+function slideJump() {
+	if (window.location.hash == null) return;
+	var sregex = /^#slide(\d+)$/;
+	var matches = sregex.exec(window.location.hash);
+	var dest = null;
+	if (matches != null) {
+		dest = parseInt(matches[1]);
+	} else {
+		dest = findSlide(window.location.hash.slice(1));
+	}
+	if (dest != null)
+		go(dest - snum);
+}
+
+function fixLinks() {
+	var thisUri = window.location.href;
+	thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length);
+	var aelements = document.getElementsByTagName('A');
+	for (var i = 0; i < aelements.length; i++) {
+		var a = aelements[i].href;
+		var slideID = a.match('\#slide[0-9]{1,2}');
+		if ((slideID) && (slideID[0].slice(0,1) == '#')) {
+			var dest = findSlide(slideID[0].slice(1));
+			if (dest != null) {
+				if (aelements[i].addEventListener) {
+					aelements[i].addEventListener("click", new Function("e",
+						"if (document.getElementById('slideProj').disabled) return;" +
+						"go("+dest+" - snum); " +
+						"if (e.preventDefault) e.preventDefault();"), true);
+				} else if (aelements[i].attachEvent) {
+					aelements[i].attachEvent("onclick", new Function("",
+						"if (document.getElementById('slideProj').disabled) return;" +
+						"go("+dest+" - snum); " +
+						"event.returnValue = false;"));
+				}
+			}
+		}
+	}
+}
+
+function externalLinks() {
+	if (!document.getElementsByTagName) return;
+	var anchors = document.getElementsByTagName('a');
+	for (var i=0; i<anchors.length; i++) {
+		var anchor = anchors[i];
+		if (anchor.getAttribute('href') && hasValue(anchor.rel, 'external')) {
+			anchor.target = '_blank';
+			addClass(anchor,'external');
+		}
+	}
+}
+
+function createControls() {
+	var controlsDiv = document.getElementById("controls");
+	if (!controlsDiv) return;
+	var hider = ' onmouseover="showHide(\'s\');" onmouseout="showHide(\'h\');"';
+	var hideDiv, hideList = '';
+	if (controlVis == 'hidden') {
+		hideDiv = hider;
+	} else {
+		hideList = hider;
+	}
+	controlsDiv.innerHTML = '<form action="#" id="controlForm"' + hideDiv + '>' +
+	'<div id="navLinks">' +
+	'<a accesskey="t" id="toggle" href="javascript:toggle();">&#216;<\/a>' +
+	'<a accesskey="z" id="prev" href="javascript:go(-1);">&laquo;<\/a>' +
+	'<a accesskey="x" id="next" href="javascript:go(1);">&raquo;<\/a>' +
+	'<div id="navList"' + hideList + '><select id="jumplist" onchange="go(\'j\');"><\/select><\/div>' +
+	'<\/div><\/form>';
+	if (controlVis == 'hidden') {
+		var hidden = document.getElementById('navLinks');
+	} else {
+		var hidden = document.getElementById('jumplist');
+	}
+	addClass(hidden,'hideme');
+}
+
+function fontScale() {  // causes layout problems in FireFox that get fixed if browser's Reload is used; same may be true of other Gecko-based browsers
+	if (!s5mode) return false;
+	var vScale = 22;  // both yield 32 (after rounding) at 1024x768
+	var hScale = 32;  // perhaps should auto-calculate based on theme's declared value?
+	if (window.innerHeight) {
+		var vSize = window.innerHeight;
+		var hSize = window.innerWidth;
+	} else if (document.documentElement.clientHeight) {
+		var vSize = document.documentElement.clientHeight;
+		var hSize = document.documentElement.clientWidth;
+	} else if (document.body.clientHeight) {
+		var vSize = document.body.clientHeight;
+		var hSize = document.body.clientWidth;
+	} else {
+		var vSize = 700;  // assuming 1024x768, minus chrome and such
+		var hSize = 1024; // these do not account for kiosk mode or Opera Show
+	}
+	var newSize = Math.min(Math.round(vSize/vScale),Math.round(hSize/hScale));
+	fontSize(newSize + 'px');
+	if (isGe) {  // hack to counter incremental reflow bugs
+		var obj = document.getElementsByTagName('body')[0];
+		obj.style.display = 'none';
+		obj.style.display = 'block';
+	}
+}
+
+function fontSize(value) {
+	if (!(s5ss = document.getElementById('s5ss'))) {
+		if (!isIE) {
+			document.getElementsByTagName('head')[0].appendChild(s5ss = document.createElement('style'));
+			s5ss.setAttribute('media','screen, projection');
+			s5ss.setAttribute('id','s5ss');
+		} else {
+			document.createStyleSheet();
+			document.s5ss = document.styleSheets[document.styleSheets.length - 1];
+		}
+	}
+	if (!isIE) {
+		while (s5ss.lastChild) s5ss.removeChild(s5ss.lastChild);
+		s5ss.appendChild(document.createTextNode('body {font-size: ' + value + ' !important;}'));
+	} else {
+		document.s5ss.addRule('body','font-size: ' + value + ' !important;');
+	}
+}
+
+function notOperaFix() {
+	slideCSS = document.getElementById('slideProj').href;
+	var slides = document.getElementById('slideProj');
+	var outline = document.getElementById('outlineStyle');
+	slides.setAttribute('media','screen');
+	outline.disabled = true;
+	if (isGe) {
+		slides.setAttribute('href','null');   // Gecko fix
+		slides.setAttribute('href',slideCSS); // Gecko fix
+	}
+	if (isIE && document.styleSheets && document.styleSheets[0]) {
+		document.styleSheets[0].addRule('img', 'behavior: url(ui/default/iepngfix.htc)');
+		document.styleSheets[0].addRule('div', 'behavior: url(ui/default/iepngfix.htc)');
+		document.styleSheets[0].addRule('.slide', 'behavior: url(ui/default/iepngfix.htc)');
+	}
+}
+
+function getIncrementals(obj) {
+	var incrementals = new Array();
+	if (!obj) 
+		return incrementals;
+	var children = obj.childNodes;
+	for (var i = 0; i < children.length; i++) {
+		var child = children[i];
+		if (hasClass(child, 'incremental')) {
+			if (child.nodeName == 'OL' || child.nodeName == 'UL') {
+				removeClass(child, 'incremental');
+				for (var j = 0; j < child.childNodes.length; j++) {
+					if (child.childNodes[j].nodeType == 1) {
+						addClass(child.childNodes[j], 'incremental');
+					}
+				}
+			} else {
+				incrementals[incrementals.length] = child;
+				removeClass(child,'incremental');
+			}
+		}
+		if (hasClass(child, 'show-first')) {
+			if (child.nodeName == 'OL' || child.nodeName == 'UL') {
+				removeClass(child, 'show-first');
+				if (child.childNodes[isGe].nodeType == 1) {
+					removeClass(child.childNodes[isGe], 'incremental');
+				}
+			} else {
+				incrementals[incrementals.length] = child;
+			}
+		}
+		incrementals = incrementals.concat(getIncrementals(child));
+	}
+	return incrementals;
+}
+
+function createIncrementals() {
+	var incrementals = new Array();
+	for (var i = 0; i < smax; i++) {
+		incrementals[i] = getIncrementals(document.getElementById('slide'+i));
+	}
+	return incrementals;
+}
+
+function defaultCheck() {
+	var allMetas = document.getElementsByTagName('meta');
+	for (var i = 0; i< allMetas.length; i++) {
+		if (allMetas[i].name == 'defaultView') {
+			defaultView = allMetas[i].content;
+		}
+		if (allMetas[i].name == 'controlVis') {
+			controlVis = allMetas[i].content;
+		}
+	}
+}
+
+// Key trap fix, new function body for trap()
+function trap(e) {
+	if (!e) {
+		e = event;
+		e.which = e.keyCode;
+	}
+	try {
+		modifierKey = e.ctrlKey || e.altKey || e.metaKey;
+	}
+	catch(e) {
+		modifierKey = false;
+	}
+	return modifierKey || e.which == 0;
+}
+
+function startup() {
+	defaultCheck();
+	if (!isOp) 
+		createControls();
+	slideLabel();
+	fixLinks();
+	externalLinks();
+	fontScale();
+	if (!isOp) {
+		notOperaFix();
+		incrementals = createIncrementals();
+		slideJump();
+		if (defaultView == 'outline') {
+			toggle();
+		}
+		document.onkeyup = keys;
+		document.onkeypress = trap;
+		document.onclick = clicker;
+	}
+}
+
+window.onload = startup;
+window.onresize = function(){setTimeout('fontScale()', 50);}
\ No newline at end of file
diff --git a/themes/xmin/.gitignore b/themes/xmin/.gitignore
new file mode 100644
index 0000000..ce130a0
--- /dev/null
+++ b/themes/xmin/.gitignore
@@ -0,0 +1,5 @@
+.Rproj.user
+.Rhistory
+.RData
+.Ruserdata
+exampleSite/public
diff --git a/LICENSE.md b/themes/xmin/LICENSE.md
index fa77e18..fa77e18 100644
--- a/LICENSE.md
+++ b/themes/xmin/LICENSE.md
diff --git a/README.md b/themes/xmin/README.md
index 3cd9a40..3cd9a40 100644
--- a/README.md
+++ b/themes/xmin/README.md
diff --git a/archetypes/default.md b/themes/xmin/archetypes/default.md
index fb98e92..fb98e92 100644
--- a/archetypes/default.md
+++ b/themes/xmin/archetypes/default.md
diff --git a/exampleSite/config.toml b/themes/xmin/exampleSite/config.toml
index 7147426..7147426 100644
--- a/exampleSite/config.toml
+++ b/themes/xmin/exampleSite/config.toml
diff --git a/exampleSite/content/_index.Rmarkdown b/themes/xmin/exampleSite/content/_index.Rmarkdown
index 9819e5d..9819e5d 100644
--- a/exampleSite/content/_index.Rmarkdown
+++ b/themes/xmin/exampleSite/content/_index.Rmarkdown
diff --git a/exampleSite/content/_index.markdown b/themes/xmin/exampleSite/content/_index.markdown
index ac2271c..ac2271c 100644
--- a/exampleSite/content/_index.markdown
+++ b/themes/xmin/exampleSite/content/_index.markdown
diff --git a/exampleSite/content/about.md b/themes/xmin/exampleSite/content/about.md
index dd83942..dd83942 100644
--- a/exampleSite/content/about.md
+++ b/themes/xmin/exampleSite/content/about.md
diff --git a/exampleSite/content/note/2017-06-13-a-quick-note.md b/themes/xmin/exampleSite/content/note/2017-06-13-a-quick-note.md
index 9d855a4..9d855a4 100644
--- a/exampleSite/content/note/2017-06-13-a-quick-note.md
+++ b/themes/xmin/exampleSite/content/note/2017-06-13-a-quick-note.md
diff --git a/exampleSite/content/note/2017-06-14-another-note.md b/themes/xmin/exampleSite/content/note/2017-06-14-another-note.md
index 16f41de..16f41de 100644
--- a/exampleSite/content/note/2017-06-14-another-note.md
+++ b/themes/xmin/exampleSite/content/note/2017-06-14-another-note.md
diff --git a/exampleSite/content/post/2015-07-23-lorem-ipsum.md b/themes/xmin/exampleSite/content/post/2015-07-23-lorem-ipsum.md
index ef58622..ef58622 100644
--- a/exampleSite/content/post/2015-07-23-lorem-ipsum.md
+++ b/themes/xmin/exampleSite/content/post/2015-07-23-lorem-ipsum.md
diff --git a/exampleSite/content/post/2016-02-14-hello-markdown.md b/themes/xmin/exampleSite/content/post/2016-02-14-hello-markdown.md
index e06e31a..e06e31a 100644
--- a/exampleSite/content/post/2016-02-14-hello-markdown.md
+++ b/themes/xmin/exampleSite/content/post/2016-02-14-hello-markdown.md
diff --git a/exampleSite/layouts/partials/foot_custom.html b/themes/xmin/exampleSite/layouts/partials/foot_custom.html
index 270a44d..270a44d 100644
--- a/exampleSite/layouts/partials/foot_custom.html
+++ b/themes/xmin/exampleSite/layouts/partials/foot_custom.html
diff --git a/hugo-xmin.Rproj b/themes/xmin/hugo-xmin.Rproj
index d64e28b..d64e28b 100644
--- a/hugo-xmin.Rproj
+++ b/themes/xmin/hugo-xmin.Rproj
diff --git a/images/screenshot.png b/themes/xmin/images/screenshot.png
index 3df0a14..3df0a14 100644
--- a/images/screenshot.png
+++ b/themes/xmin/images/screenshot.png
Binary files differdiff --git a/images/tn.png b/themes/xmin/images/tn.png
index 389a20c..389a20c 100644
--- a/images/tn.png
+++ b/themes/xmin/images/tn.png
Binary files differdiff --git a/layouts/404.html b/themes/xmin/layouts/404.html
index c2e4e40..c2e4e40 100644
--- a/layouts/404.html
+++ b/themes/xmin/layouts/404.html
diff --git a/layouts/_default/list.html b/themes/xmin/layouts/_default/list.html
index 06b290a..06b290a 100644
--- a/layouts/_default/list.html
+++ b/themes/xmin/layouts/_default/list.html
diff --git a/layouts/_default/single.html b/themes/xmin/layouts/_default/single.html
index de3f121..de3f121 100644
--- a/layouts/_default/single.html
+++ b/themes/xmin/layouts/_default/single.html
diff --git a/layouts/_default/terms.html b/themes/xmin/layouts/_default/terms.html
index 71f47e7..71f47e7 100644
--- a/layouts/_default/terms.html
+++ b/themes/xmin/layouts/_default/terms.html
diff --git a/layouts/partials/foot_custom.html b/themes/xmin/layouts/partials/foot_custom.html
index e69de29..e69de29 100644
--- a/layouts/partials/foot_custom.html
+++ b/themes/xmin/layouts/partials/foot_custom.html
diff --git a/layouts/partials/footer.html b/themes/xmin/layouts/partials/footer.html
index 3f46ea5..3f46ea5 100644
--- a/layouts/partials/footer.html
+++ b/themes/xmin/layouts/partials/footer.html
diff --git a/layouts/partials/head_custom.html b/themes/xmin/layouts/partials/head_custom.html
index e69de29..e69de29 100644
--- a/layouts/partials/head_custom.html
+++ b/themes/xmin/layouts/partials/head_custom.html
diff --git a/layouts/partials/header.html b/themes/xmin/layouts/partials/header.html
index 4f431eb..4f431eb 100644
--- a/layouts/partials/header.html
+++ b/themes/xmin/layouts/partials/header.html
diff --git a/static/css/fonts.css b/themes/xmin/static/css/fonts.css
index 8ffcecd..8ffcecd 100644
--- a/static/css/fonts.css
+++ b/themes/xmin/static/css/fonts.css
diff --git a/static/css/style.css b/themes/xmin/static/css/style.css
index 4dc3ae4..4dc3ae4 100644
--- a/static/css/style.css
+++ b/themes/xmin/static/css/style.css
diff --git a/theme.toml b/themes/xmin/theme.toml
index 8733afd..8733afd 100644
--- a/theme.toml
+++ b/themes/xmin/theme.toml