diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..2fe5e9b
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,48 @@
+version: 2.1
+
+# Default actions to perform on each Emacs version
+commands:
+  default-steps:
+    steps:
+      - checkout
+      - run: make version
+      - run: make lint
+
+# Enumerated list of Emacs versions
+jobs:
+  test-emacs-25:
+    docker:
+      - image: silex/emacs:25-ci
+        entrypoint: bash
+    steps:
+      - default-steps
+
+  test-emacs-26:
+    docker:
+      - image: silex/emacs:26-ci
+        entrypoint: bash
+    steps:
+      - default-steps
+
+  test-emacs-27:
+    docker:
+      - image: silex/emacs:27-ci
+        entrypoint: bash
+    steps:
+      - default-steps
+
+  test-emacs-master:
+    docker:
+      - image: silex/emacs:master-ci
+        entrypoint: bash
+    steps:
+      - default-steps
+
+# Executing in parallel
+workflows:
+  ci-test-matrix:
+    jobs:
+      - test-emacs-25
+      - test-emacs-26
+      - test-emacs-27
+      - test-emacs-master
diff --git a/.emacs/.gitignore b/.emacs/.gitignore
new file mode 100644
index 0000000..741a67f
--- /dev/null
+++ b/.emacs/.gitignore
@@ -0,0 +1,5 @@
+.emacs-custom.el
+network-security.data
+elpa/
+quelpa/
+
diff --git a/.emacs/dependencies.el b/.emacs/dependencies.el
new file mode 100644
index 0000000..eba9cd3
--- /dev/null
+++ b/.emacs/dependencies.el
@@ -0,0 +1,6 @@
+;;; dependencies.el - project specific package dependencies
+
+(use-package elisp-lint
+  :ensure t)
+
+;;; dependencies.el ends here
diff --git a/.emacs/init.el b/.emacs/init.el
new file mode 100644
index 0000000..77ce7c6
--- /dev/null
+++ b/.emacs/init.el
@@ -0,0 +1,59 @@
+;;; init.el - Emacs initialization for isolated package testing
+;;
+;; Usage: emacs -q -l $project_root/emacs/init.el
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Set `user-emacs-directory' to avoid overwriting $HOME/.emacs.d
+;; See also: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=15539#66
+
+(setq user-init-file (or load-file-name (buffer-file-name)))
+(setq user-emacs-directory (file-name-directory user-init-file))
+(setq custom-file (concat user-emacs-directory ".emacs-custom.el"))
+(when (file-readable-p custom-file) (load custom-file))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Configure melpa and melpa-stable
+
+(require 'package)
+(add-to-list 'package-archives
+             '("melpa-stable" . "https://stable.melpa.org/packages/") t)
+(add-to-list 'package-archives
+             '("melpa"        . "https://melpa.org/packages/") t)
+(setq package-enable-at-startup nil)
+(package-initialize)
+(when (not package-archive-contents)
+    (package-refresh-contents))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Bootstrap `use-package'
+
+;; By default these will install from melpa anyway, but using
+;; `package-pinned-packages' allows one to pin to melpa-stable
+;; if necessary
+
+(setq package-pinned-packages
+      '((bind-key           . "melpa")
+        (diminish           . "melpa")
+        (use-package        . "melpa")))
+
+(dolist (p (mapcar 'car package-pinned-packages))
+  (unless (package-installed-p p)
+    (package-install p)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Install `quelpa' and `quelpa-use-package'
+
+(use-package quelpa
+  ;; :pin melpa-stable
+  :ensure t)
+
+(use-package quelpa-use-package
+  ;; :pin melpa-stable
+  :ensure t)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Load project dependencies from elsewhere
+
+(load (concat user-emacs-directory "dependencies.el"))
+
+;;; init.el ends here
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..304830b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.elpa
+*.elc
+*-autoloads.el
+*~
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..4f9136b
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1,3 @@
+.custom.el*
+elpa/
+*.png
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..66fa4f0
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,51 @@
+# Recipe for converting video to animated gif using ffmpeg
+# Reference: https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg/
+
+VIDEOS = dimmer-demo.webm
+
+GIFS = $(VIDEOS:.webm=.gif)
+
+default: $(GIFS)
+
+clean:
+	rm -f $(GIFS)
+
+%.gif : %.webm
+	ffmpeg -i $< -filter_complex "[0:v] fps=15,scale=w=640:h=-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $@
+
+
+## Script to generate screenshot gallery
+
+EMACS ?= emacs
+
+GALLERY_TMPL = "(go :MODE FRAC 'THEME)"
+GALLERY_THEMES = zenburn \
+                 solarized-light \
+                 solarized-dark \
+                 spacemacs-light \
+                 spacemacs-dark \
+                 tomorrow-day \
+                 tomorrow-night \
+                 tomorrow-blue \
+                 tomorrow-bright \
+                 tomorrow-eighties
+GALLERY_MODES = foreground background both
+GALLERY_FRACS = -0.2 -0.1 0.1 0.2 0.4
+GALLERY_CASES = $(foreach theme,$(GALLERY_THEMES), \
+                  $(foreach mode,$(GALLERY_MODES), \
+                    $(subst MODE,$(mode), \
+                      $(subst THEME,$(theme),example-THEME-MODE))))
+GALLERY = $(foreach theme,$(GALLERY_THEMES), \
+            $(foreach mode,$(GALLERY_MODES), \
+              $(foreach frac,$(GALLERY_FRACS), \
+                $(subst FRAC,$(frac), \
+                  $(subst MODE,$(mode), \
+                    $(subst THEME,$(theme),$(GALLERY_TMPL)))))))
+
+gallery:
+	for i in ${GALLERY} ; do \
+	    ${EMACS} -q -l gallery.el --eval "$$i" ;\
+	done
+	for i in ${GALLERY_CASES} ; do \
+	    python comp.py $$i ${GALLERY_FRACS} ;\
+	done
diff --git a/doc/comp.py b/doc/comp.py
new file mode 100644
index 0000000..c5734ab
--- /dev/null
+++ b/doc/comp.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+from PIL import Image, ImageFont, ImageDraw
+import sys
+
+im1 = Image.open(sys.argv[1]+"-"+sys.argv[2]+".png")
+im2 = Image.open(sys.argv[1]+"-"+sys.argv[3]+".png")
+im3 = Image.open(sys.argv[1]+"-"+sys.argv[4]+".png")
+im4 = Image.open(sys.argv[1]+"-"+sys.argv[5]+".png")
+im5 = Image.open(sys.argv[1]+"-"+sys.argv[6]+".png")
+
+f = ImageFont.truetype("~/Library/Fonts/Inconsolata-Regular.ttf", 24)
+
+c0 = im1.crop((0,230,200,630))
+d0 = ImageDraw.Draw(c0)
+d0.text((90,370), "orig", font=f)
+c1 = im1.crop((1280,230,1480,630))
+d1 = ImageDraw.Draw(c1)
+d1.text((90,370), sys.argv[2], font=f)
+c2 = im2.crop((1280,230,1480,630))
+d2 = ImageDraw.Draw(c2)
+d2.text((90,370), sys.argv[3], font=f)
+c3 = im3.crop((1280,230,1480,630))
+d3 = ImageDraw.Draw(c3)
+d3.text((90,370), sys.argv[4], font=f)
+c4 = im4.crop((1280,230,1480,630))
+d4 = ImageDraw.Draw(c4)
+d4.text((90,370), sys.argv[5], font=f)
+c5 = im5.crop((1280,230,1480,630))
+d5 = ImageDraw.Draw(c5)
+d5.text((90,370), sys.argv[6], font=f)
+
+im = Image.new("RGB", (1200,400))
+
+im.paste(c1, (0,0))
+im.paste(c2, (200,0))
+im.paste(c0, (400,0))
+im.paste(c3, (600,0))
+im.paste(c4, (800,0))
+im.paste(c5, (1000,0))
+
+im.save(sys.argv[1]+"-comp.png")
+
diff --git a/doc/gallery.el b/doc/gallery.el
new file mode 100644
index 0000000..ebb37d5
--- /dev/null
+++ b/doc/gallery.el
@@ -0,0 +1,134 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; run with:
+;;    /usr/local/bin/emacs -q -l gallery.el --eval "(go :background 0.4 'solarized-dark)"
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; set user-emacs-directory to the directory where this file lives
+;; do not read or write anything in $HOME/.emacs.d
+
+(setq user-init-file (or load-file-name (buffer-file-name)))
+(setq user-emacs-directory (file-name-directory user-init-file))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; set custom-file to write into a separate place
+
+(setq custom-file (concat user-emacs-directory ".custom.el"))
+(when (file-readable-p custom-file) (load custom-file))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; configure melpa (and other repos if needed)
+
+(require 'package)
+(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
+(setq package-enable-at-startup nil)
+(package-initialize)
+(when (not package-archive-contents)
+    (package-refresh-contents))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; bootstrap `use-package'
+
+(setq package-pinned-packages
+      '((bind-key           . "melpa")
+        (diminish           . "melpa")
+        (use-package        . "melpa")))
+
+(dolist (p (mapcar 'car package-pinned-packages))
+  (unless (package-installed-p p)
+    (package-install p)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; install and configure packages
+
+;; https://emacsthemes.com/popular/index.html
+
+(use-package zenburn-theme
+  :ensure t)
+
+(use-package solarized-theme
+  :ensure t
+  :config
+  ;(load-theme 'solarized-dark t)
+  ;(load-theme 'solarized-light t)
+  )
+
+(use-package spacemacs-common
+  :ensure spacemacs-theme
+  :init
+  ;(load-theme 'spacemacs-light)
+  ;(load-theme 'spacemacs-dark)
+  )
+
+(use-package color-theme-sanityinc-tomorrow
+  :ensure t
+  :init
+  ;(load-theme sanityinc-tomorrow-day)
+  ;(load-theme sanityinc-tomorrow-night)
+  ;(load-theme sanityinc-tomorrow-blue)
+  ;(load-theme sanityinc-tomorrow-bright)
+  ;(load-theme sanityinc-tomorrow-eighties)
+  )
+
+(use-package dimmer
+  :ensure t)
+
+(add-to-list 'default-frame-alist '(fullscreen . maximized))
+(add-to-list 'default-frame-alist '(font . "Inconsolata-12"))
+(setq inhibit-splash-screen t)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun go-gallery (file &optional line)
+  (with-current-buffer (find-file file)
+    (setq buffer-file-name nil)
+    (rename-buffer "dimmed")
+    (delete-other-windows)
+    (and line (goto-line line))
+    (split-window-right))
+  (with-current-buffer (find-file file)
+    (setq buffer-file-name nil)
+    (rename-buffer "default")
+    (and line (goto-line line))))
+
+(defun go-dimmer (mode frac)
+  (setq dimmer-adjustment-mode mode)
+  (setq dimmer-fraction frac)
+  (dimmer-mode 1))
+
+;; https://emacsthemes.com/popular/index.html
+(defun go-theme (theme)
+  (cond ((eq theme 'zenburn))
+        ((eq theme 'solarized-dark)
+         (load-theme 'solarized-dark t))
+        ((eq theme 'solarized-light)
+         (load-theme 'solarized-light t))
+        ((eq theme 'spacemacs-dark)
+         (load-theme 'spacemacs-dark t))
+        ((eq theme 'spacemacs-light)
+         (load-theme 'spacemacs-light t))
+        ((eq theme 'tomorrow-day)
+         (load-theme 'sanityinc-tomorrow-day t))
+        ((eq theme 'tomorrow-night)
+         (load-theme 'sanityinc-tomorrow-night t))
+        ((eq theme 'tomorrow-blue)
+         (load-theme 'sanityinc-tomorrow-blue t))
+        ((eq theme 'tomorrow-bright)
+         (load-theme 'sanityinc-tomorrow-bright t))
+        ((eq theme 'tomorrow-eighties)
+         (load-theme 'sanityinc-tomorrow-eighties t))))
+
+(defun go (mode frac theme)
+  (go-dimmer mode frac)
+  (go-theme theme)
+  (go-gallery "../dimmer.el" 310)
+  (setq grab-file (format "example-%s-%s-%s.png"
+                          theme
+                          (substring (symbol-name mode) 1)
+                          frac)))
+
+(defun grab ()
+  (interactive)
+  (shell-command (format "screencapture -R0,80,1280,673 %s" (concat user-emacs-directory grab-file)))
+  (kill-emacs))
+
+(global-set-key (kbd "<f3>") #'grab)