diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# pandoc-sidenote
+
+> Convert Pandoc Markdown-style footnotes into sidenotes
+
+This is a simple [Pandoc filter] to convert footnotes into a format that can be
+consumed by [Tufte CSS]. On the whole, this project weighs in at well under 100
+lines of code. Check out [SideNote.hs](src/Text/Pandoc/SideNote.hs) if you're curious how it works.
+
+It's used by calling `pandoc --filter pandoc-sidenote`. To see it in action, see
+[Tufte Pandoc CSS], a project which uses it. In particular, take a look at the
+Makefile included in that project.
+
+The core functionality is also exposed as a library, which can be called by Haskell
+applications such as Hakyll.
+
+## Dependencies
+
+`pandoc-sidenote` is build against a specific version of Pandoc. This table maps
+`pandoc` versions to `pandoc-sidenote` versions:
+
+| pandoc    | pandoc-sidenote |
+| ------    | --------------- |
+| 2.9       | 0.20.0          |
+| 2.1, 1.19 | 0.19.0          |
+| 1.18      | 0.9.0           |
+
+If a newer version of `pandoc` has been released, the Stack build manifest
+will need to be adjusted for that version, and the project then rebuilt.
+
+## Installation
+
+### Cabal
+
+`pandoc-sidenote` is on Hackage and can thus be installed using `cabal`:
+
+```bash
+cabal install pandoc-sidenote
+```
+
+### Homebrew
+
+If you're on OS X, you can install the `pandoc-sidenote` binary from my Homebrew
+tap:
+
+```bash
+brew install jez/formulae/pandoc-sidenote
+```
+
+### From Source
+
+Otherwise, you'll have to install from source. This project is written in
+Haskell and built using [Stack]. If you're new to Haskell, now's a perfect time
+to wet your toes! Go [install Stack first], then run these commands:
+
+```bash
+git clone https://github.com/jez/pandoc-sidenote
+
+cd pandoc-sidenote
+
+# this is going to be reaaally long the first time
+stack build
+
+# copy the compiled binary onto your PATH
+stack install
+```
+
+## License
+
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://jez.io/MIT-LICENSE.txt)
+
+[Tufte CSS]: https://edwardtufte.github.io/tufte-css/
+[Stack]: https://docs.haskellstack.org/en/stable/README/
+[install Stack first]: https://docs.haskellstack.org/en/stable/README/
+[Pandoc filter]: http://pandoc.org/scripting.html#json-filters
+[Tufte Pandoc CSS]: https://github.com/jez/tufte-pandoc-css
diff --git a/pandoc-sidenote.cabal b/pandoc-sidenote.cabal
--- a/pandoc-sidenote.cabal
+++ b/pandoc-sidenote.cabal
@@ -1,36 +1,58 @@
-name:                pandoc-sidenote
-version:             0.19.0.0
-synopsis:            Convert Pandoc Markdown-style footnotes into sidenotes
-description:         Convert Pandoc Markdown-style footnotes into sidenotes
-homepage:            https://github.com/jez/pandoc-sidenote#readme
-license:             MIT
-license-file:        LICENSE
-author:              Jake Zimmerman
-maintainer:          zimmerman.jake@gmail.com
-copyright:           2016 Jake Zimmerman
-category:            CommandLine
-build-type:          Simple
--- extra-source-files:
-cabal-version:       >=1.10
+cabal-version: 1.12
 
-Library
-  Exposed-modules:   Text.Pandoc.SideNote
-  hs-source-dirs:      src
-  build-depends:       base < 5
-                     , monad-gen
-                     , pandoc
-                     , pandoc-types
-  default-language:    Haskell2010
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 6dc364e65c31a9cfa4710a50751353bc27d6b9a25404043839d770e41c2f207c
 
-executable pandoc-sidenote
-  hs-source-dirs:      ./
-  main-is:             Main.hs
-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
-  build-depends:       base < 5
-                     , pandoc-sidenote
-                     , pandoc-types
-  default-language:    Haskell2010
+name:           pandoc-sidenote
+version:        0.20.0.0
+synopsis:       Convert Pandoc Markdown-style footnotes into sidenotes
+description:    This is a simple Pandoc filter to convert footnotes into a format that can be consumed by Tufte CSS. On the whole, this project weighs in at well under 100 lines of code. Check out SideNote.hs if you're curious how it works.
+category:       CommandLine
+homepage:       https://github.com/jez/pandoc-sidenote#readme
+bug-reports:    https://github.com/jez/pandoc-sidenote/issues
+author:         Jake Zimmerman
+maintainer:     zimmerman.jake@gmail.com
+copyright:      2016 Jake Zimmerman
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    LICENSE
 
 source-repository head
-  type:     git
+  type: git
   location: https://github.com/jez/pandoc-sidenote
+
+library
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Wmissing-signatures -funbox-strict-fields
+  build-depends:
+      base >=4.7 && <5
+    , mtl
+    , pandoc-types >=1.20
+    , text
+  exposed-modules:
+      Text.Pandoc.SideNote
+  other-modules:
+      Paths_pandoc_sidenote
+  default-language: Haskell2010
+
+executable pandoc-sidenote
+  main-is: Main.hs
+  hs-source-dirs:
+      ./
+  ghc-options: -Wall -Wcompat -Wmissing-signatures -funbox-strict-fields -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , mtl
+    , pandoc-sidenote
+    , pandoc-types >=1.20
+    , text
+  other-modules:
+      Paths_pandoc_sidenote
+  default-language: Haskell2010
diff --git a/src/Text/Pandoc/SideNote.hs b/src/Text/Pandoc/SideNote.hs
--- a/src/Text/Pandoc/SideNote.hs
+++ b/src/Text/Pandoc/SideNote.hs
@@ -1,68 +1,93 @@
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Text.Pandoc.SideNote (usingSideNotes) where
 
-import Data.List (intercalate)
+import           Data.List           (intercalate)
+import           Data.Text           (Text, append, pack)
 
-import Control.Monad.Gen
+import           Control.Monad.State
 
-import Text.Pandoc.Walk (walk, walkM)
-import Text.Pandoc.JSON
+import           Text.Pandoc.JSON
+import           Text.Pandoc.Walk    (walk, walkM)
 
-getFirstStr :: [Inline] -> Maybe String
-getFirstStr [] = Nothing
-getFirstStr (Str text : _) = Just text
-getFirstStr (_ : inlines) = getFirstStr inlines
+getFirstStr :: [Inline] -> Maybe Text
+getFirstStr []                 = Nothing
+getFirstStr (Str text:_      ) = Just text
+getFirstStr (_       :inlines) = getFirstStr inlines
 
 newline :: [Inline]
 newline = [LineBreak, LineBreak]
 
+-- This could be implemented more concisely, but I think this is more clear.
+getThenIncr :: State Int Int
+getThenIncr = do
+  i <- get
+  put (i + 1)
+  return i
+
 -- Extract inlines from blocks
 coerceToInline :: [Block] -> [Inline]
 coerceToInline = concatMap deBlock . walk deNote
-  where deBlock :: Block -> [Inline]
-        deBlock (Plain ls) = ls
-        -- Simulate paragraphs with double LineBreak
-        deBlock (Para ls) = ls ++ newline
-        -- See extension: line_blocks
-        deBlock (LineBlock lss) = intercalate [LineBreak] lss ++ newline
-        -- Pretend RawBlock is RawInline (might not work!)
-        -- Consider: raw <div> now inside RawInline... what happens?
-        deBlock (RawBlock fmt str) = [RawInline fmt str]
-        -- lists, blockquotes, headers, hrs, and tables are all omitted
-        -- Think they shouldn't be? I'm open to sensible PR's.
-        deBlock _ = []
+ where
+  deBlock :: Block -> [Inline]
+  deBlock (Plain     ls    ) = ls
+  -- Simulate paragraphs with double LineBreak
+  deBlock (Para      ls    ) = ls ++ newline
+  -- See extension: line_blocks
+  deBlock (LineBlock lss   ) = intercalate [LineBreak] lss ++ newline
+  -- Pretend RawBlock is RawInline (might not work!)
+  -- Consider: raw <div> now inside RawInline... what happens?
+  deBlock (RawBlock fmt str) = [RawInline fmt str]
+  -- lists, blockquotes, headers, hrs, and tables are all omitted
+  -- Think they shouldn't be? I'm open to sensible PR's.
+  deBlock _                  = []
 
-        deNote (Note _) = Str ""
-        deNote x        = x
+  deNote (Note _) = Str ""
+  deNote x        = x
 
-filterInline :: Inline -> Gen Int Inline
+filterInline :: Inline -> State Int Inline
 filterInline (Note blocks) = do
   -- Generate a unique number for the 'for=' attribute
-  i <- gen
+  i <- getThenIncr
 
   -- Note has a [Block], but Span needs [Inline]
-  let content = coerceToInline blocks
+  let content  = coerceToInline blocks
 
   -- The '{-}' symbol differentiates between margin note and side note
-  let nonu = getFirstStr content == Just "{-}"
+  let nonu     = getFirstStr content == Just "{-}"
   let content' = if nonu then tail content else content
 
-  let labelCls = "margin-toggle" ++ (if nonu then "" else " sidenote-number")
+  let labelCls = "margin-toggle" `append`
+                 (if nonu then "" else " sidenote-number")
   let labelSym = if nonu then "&#8853;" else ""
-  let labelHTML = "<label for=\"sn-" ++ show i ++ "\" class=\"" ++ labelCls ++ "\">" ++ labelSym ++ "</label>"
+  let labelHTML = mconcat
+         [ "<label for=\"sn-"
+         , pack (show i)
+         , "\" class=\""
+         , labelCls
+         , "\">"
+         , labelSym
+         , "</label>"
+         ]
   let label = RawInline (Format "html") labelHTML
 
-  let inputHTML = "<input type=\"checkbox\" id=\"sn-" ++ show i ++ "\" " ++ "class=\"margin-toggle\"/>"
-  let input = RawInline (Format "html") inputHTML
+  let inputHTML = mconcat
+        [ "<input type=\"checkbox\" id=\"sn-"
+        , pack (show i)
+        , "\" "
+        , "class=\"margin-toggle\"/>"
+        ]
+  let input             = RawInline (Format "html") inputHTML
 
   let (ident, _, attrs) = nullAttr
-  let noteTypeCls = if nonu then "marginnote" else "sidenote"
-  let note = Span (ident, [noteTypeCls], attrs) content'
+  let noteTypeCls       = if nonu then "marginnote" else "sidenote"
+  let note              = Span (ident, [noteTypeCls], attrs) content'
 
   return $ Span nullAttr [label, input, note]
 
 filterInline inline = return inline
 
 usingSideNotes :: Pandoc -> Pandoc
-usingSideNotes (Pandoc meta blocks) = Pandoc meta (runGen (walkM filterInline blocks))
+usingSideNotes (Pandoc meta blocks) =
+  Pandoc meta (evalState (walkM filterInline blocks) 0)
