diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,14 @@
+# Change log
+
+## v0.0.2 (2015-03-18)
+
+-   Added a rudimentary user interface.
+-   Moved HTML into a separate file.
+
+## v0.0.1 (2015-03-18)
+
+-   Initially released.
+
+## v0.0.0 (2015-03-17)
+
+-   Initially created.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Blunt
+
+Point-free Haskell as a service.
diff --git a/blunt.cabal b/blunt.cabal
--- a/blunt.cabal
+++ b/blunt.cabal
@@ -1,5 +1,5 @@
 name: blunt
-version: 0.0.1
+version: 0.0.2
 cabal-version: >=1.10
 build-type: Simple
 license: MIT
@@ -9,6 +9,12 @@
 description:
     TODO
 category: Web
+data-files:
+    index.html
+data-dir: data
+extra-source-files:
+    CHANGELOG.md
+    README.md
 
 source-repository head
     type: git
@@ -25,6 +31,8 @@
         warp ==3.*
     default-language: Haskell2010
     hs-source-dirs: library
+    other-modules:
+        Paths_blunt
     ghc-options: -Wall
 
     -- pointfree
diff --git a/data/index.html b/data/index.html
new file mode 100644
--- /dev/null
+++ b/data/index.html
@@ -0,0 +1,53 @@
+<!doctype html>
+
+<html>
+  <head>
+    <meta name="viewport" content="initial-scale = 1, width = device-width">
+
+    <title>Blunt</title>
+  </head>
+
+  <body>
+    <h1>Blunt</h1>
+
+    <dl>
+      <dt>Input</dt>
+      <dd>
+        <input
+          id="input"
+          placeholder="sum xs = foldr (+) 0 xs"
+          autofocus>
+      </dd>
+
+      <dt>Output</dt>
+      <dd>
+        <input
+          id="output"
+          placeholder="sum = foldr (+) 0"
+          readonly>
+      </dd>
+    </dl>
+
+    <script>
+      'use strict';
+
+      (function () {
+        var input = document.getElementById('input');
+        var output = document.getElementById('output');
+
+        input.oninput = function (_event) {
+          var request = new XMLHttpRequest();
+
+          request.onreadystatechange = function () {
+            if (request.readyState === 4 && request.status === 200) {
+              output.value = request.response;
+            }
+          };
+          request.open('GET',
+            '/pointfree?input=' + encodeURIComponent(input.value));
+          request.send();
+        };
+      }());
+    </script>
+  </body>
+</html>
diff --git a/library/Blunt.hs b/library/Blunt.hs
--- a/library/Blunt.hs
+++ b/library/Blunt.hs
@@ -2,53 +2,28 @@
 
 module Blunt where
 
+import Paths_blunt (getDataFileName)
+
 import Data.ByteString.Char8 (unpack)
 import Data.ByteString.Lazy (fromStrict)
 import Data.ByteString.Lazy.Char8 (pack)
 import Network.HTTP.Types (notFound404, ok200)
-import Network.Wai (queryString, pathInfo, requestMethod, responseLBS)
+import Network.Wai (queryString, pathInfo, requestMethod, responseFile,
+    responseLBS)
 import Network.Wai.Handler.Warp (run)
 import Pointfree (pointfree')
 
 main :: IO ()
 main = run 8080 $ \ request respond -> do
+    index <- getDataFileName "index.html"
     let method = requestMethod request
         path = pathInfo request
         response = case (method, path) of
-            ("GET", []) -> responseLBS ok200 [("Content-Type", "text/html; charset=utf-8")] $ pack $ unlines
-                [ "<!doctype html>"
-                , ""
-                , "<html>"
-                , "  <head>"
-                , "    <title>Pointfree</title>"
-                , "  </head>"
-                , ""
-                , "  <body>"
-                , "    <input id='input' autofocus>"
-                , "    <input id='output' readonly>"
-                , ""
-                , "    <script>"
-                , "      'use strict';"
-                , ""
-                , "      (function () {"
-                , "        var input = document.getElementById('input');"
-                , "        var output = document.getElementById('output');"
-                , ""
-                , "        input.oninput = function (event) {"
-                , "          var request = new XMLHttpRequest();"
-                , "          request.onreadystatechange = function () {"
-                , "            if (request.readyState === 4 && request.status === 200) {"
-                , "              output.value = request.response;"
-                , "            }"
-                , "          };"
-                , "          request.open('GET', '/pointfree?input=' + encodeURIComponent(input.value));"
-                , "          request.send();"
-                , "        };"
-                , "      }());"
-                , "    </script>"
-                , "  </body>"
-                , "</html>"
-                ]
+            ("GET", []) -> responseFile
+                ok200
+                [("Content-Type", "text/html; charset=utf-8")]
+                index
+                Nothing
             ("GET", ["pointfree"]) ->
                 let params = queryString request
                     input = case lookup "input" params of
