diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+Tianbar
+=======
+
+Tianbar is a status bar for XMonad and possibly similar window managers. It is
+using WebKit as its rendering engine, meaning that the entire look and feel is
+customizable using HTML, CSS and JavaScript.
+
+Usage
+-----
+
+Tianbar will show `index.html` from its [XDG configuration directory][xdg]
+(usually `$HOME/.config/taffybar`). It it up to you to create that, add styles,
+widgets and other behavior.
+
+Widgets
+-------
+
+A small collection of widgets written in JavaScript is bundled with Tianbar,
+see `scripts` directory. To include them on your page, use `tianbar:` URL
+scheme, e.g. `tianbar:scripts/time.js`.
+
+Currently widgets require [jQuery][jquery] and [RequireJS][requirejs], and
+neither of those is distributed with Tianbar -- you will have to obtain them
+manually.
+
+XMonad integration
+------------------
+
+XMonad status widget works by listening on DBus for messages sent by the
+provided `logHook`, see `System.Tianbar.XMonadLog` documentation for details.
+
+Quirks
+------
+
+* AJAX requests from Tianbar are not subject to the same origin policy. This
+  makes it easier to interact with various Web services. You can also make
+  requests to the local files, for example, to extract CPU activity statistics
+  from `/proc/stat`.
+* HTML5 geolocation API does not work. A shim, which uses
+  [freegeoip.net][freegeoip], is provided instead.
+* Interaction with the displayed Web page is limited. For example, text fields
+  are not active if there are other windows on the screen, and opening new
+  windows does not work.
+
+Acknowledgements
+----------------
+
+The project is essentially a fork of [Taffybar][taffybar], stripped down of
+Haskell configuration and widgets and not yet having achieved functional parity
+with it.
+
+[freegeoip]: http://freegeoip.net/
+[jquery]: http://jquery.com/
+[requirejs]: http://requirejs.org/
+[taffybar]: https://github.com/travitch/taffybar
+[xdg]: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
diff --git a/scripts/location_shim.js b/scripts/location_shim.js
new file mode 100644
--- /dev/null
+++ b/scripts/location_shim.js
@@ -0,0 +1,20 @@
+/*
+ * Basic HTML5 geolocation shim.
+ *
+ * Requires 'jquery' to be available through RequireJS.
+ */
+define(['jquery'], function ($) {
+  var my_geolocation;
+
+  navigator.geolocation = {};
+  navigator.geolocation.getCurrentPosition = function (cb) {
+    if (my_geolocation) {
+      cb(my_geolocation);
+    } else {
+      $.getJSON('http://freegeoip.net/json/').success(function (loc) {
+        my_geolocation = { coords: loc };
+        cb(my_geolocation);
+      });
+    }
+  };
+});
diff --git a/scripts/time.js b/scripts/time.js
new file mode 100644
--- /dev/null
+++ b/scripts/time.js
@@ -0,0 +1,34 @@
+/*
+ * Clock plugin using Moment.js (http://momentjs.com/) to
+ * format the time and date.
+ *
+ * The only exposed property, 'format', determines the
+ * format (see Moment.js documentation) to display time and date.
+ *
+ * The plugin requires 'jquery', 'moment' and 'moment/lang' to be
+ * available through RequireJS.
+ */
+define(['jquery', 'moment', 'moment/lang'], function ($, moment) {
+  var config = {
+    format: 'llll'
+  };
+
+  function updateClock() {
+    var dt = moment();
+    var clockText = dt.format(config.format);
+    $('.widget-time').text(clockText);
+  }
+
+  $(document).ready(function () {
+    var lang = navigator.language;
+    if (moment.langData(lang)) {
+      moment.lang(lang);
+    } else {
+      moment.lang(navigator.language.replace(/-.+/, ''));
+    }
+    updateClock();
+    setInterval(updateClock, 1000);
+  });
+
+  return config;
+});
diff --git a/scripts/weather.js b/scripts/weather.js
new file mode 100644
--- /dev/null
+++ b/scripts/weather.js
@@ -0,0 +1,50 @@
+/*
+ * Weather plugin using Yahoo! Weather API.
+ *
+ * The plugin requires 'jquery' to be available through RequireJS.
+ *
+ * At least on webkit-0.12.4, geolocation needed by this plugin is not
+ * functional, so location_shim.js is required to be loaded before.
+ */
+define(['jquery'], function ($) {
+  var api_key = 'a54ebf597a8e77198d9a47c798d42c51';
+  var woeid;
+
+  function unescapeHTML(html) {
+    return $('<span/>').html(html).text();
+  }
+
+  function updateWeather() {
+    $.ajax('http://weather.yahooapis.com/forecastrss?' +
+      'w=' + woeid + '&u=c')
+    .success(function (weather) {
+      var temp = $('condition', weather).attr('temp');
+      var units = $('units', weather).attr('temperature');
+      units = " &deg;" + units;
+      var forecast = $('forecast', weather).map(function (k, el) {
+        el = $(el);
+        return el.attr('day') + ": " + el.attr('text') + ", " +
+          el.attr('low') + "&ndash;" + el.attr('high') +
+          units;
+      });
+      forecast = Array.prototype.join.call(forecast, '&#13;');
+      $('.widget-weather').html(temp + units);
+      $('.widget-weather').attr('title', unescapeHTML(forecast));
+    });
+  }
+
+  $(document).ready(function () {
+    navigator.geolocation.getCurrentPosition(function (pos) {
+      $.getJSON('http://query.yahooapis.com/v1/public/yql?q=' +
+        'select%20place.woeid%20from%20flickr.places%20where%20' +
+        'lat%3D' + pos.coords.latitude + '%20and%20' +
+        'lon%3D' + pos.coords.longitude + '%20and%20' +
+        'api_key=%22' + api_key + '%22&format=json')
+      .success(function (yloc) {
+        woeid = yloc.query.results.places.place.woeid;
+        updateWeather();
+        setInterval(updateWeather, 5 * 60 * 1000);
+      });
+    });
+  });
+});
diff --git a/scripts/xmonad.js b/scripts/xmonad.js
new file mode 100644
--- /dev/null
+++ b/scripts/xmonad.js
@@ -0,0 +1,31 @@
+/*
+ * XMonad status plugin, receiving workspace status over DBus.
+ * To send status, use dbusLog or dbusLogWithMarkup from
+ * System.Tianbar.XMonadLog as your XMonad logHook.
+ *
+ * The status appears in elements matching class 'widget-xmonad'.
+ *
+ * The plugin requires 'jquery' to be available through RequireJS.
+ */
+define(['jquery'], function ($) {
+  function setStatus(st) {
+    $('.widget-xmonad').html(st);
+  }
+
+  // Currently, a check for this function (and if it doesn't exist,
+  // window.XMonadStatus) is hardcoded in Tianbar. A better solution
+  // would be to expose an interface for subscribing to DBus connections
+  // from JavaScript.
+  window.setXMonadStatus = function (st) {
+    window.XMonadStatus = undefined;
+    setStatus(st);
+  };
+
+  $(document).ready(function () {
+    window.setTimeout(function () {
+      if (window.XMonadStatus) {
+        window.setXMonadStatus(window.XMonadStatus);
+      }
+    }, 1000);
+  });
+});
diff --git a/src/System/Tianbar/WebKit.hs b/src/System/Tianbar/WebKit.hs
--- a/src/System/Tianbar/WebKit.hs
+++ b/src/System/Tianbar/WebKit.hs
@@ -20,15 +20,37 @@
 
 import System.Tianbar.Configuration
 
+import Paths_tianbar
+
 gsettingsGet :: String -> String -> IO String
 gsettingsGet schema key = do
     output <- readProcess "gsettings" ["get", schema, key] []
     let len = length output
     return $ drop 1 $ take (len - 2) output
 
-gsettingsPrefix :: String
-gsettingsPrefix = "gsettings:"
+type UriOverride = String -> Maybe (IO String)
 
+withPrefix :: String -> (String -> IO String) -> UriOverride
+withPrefix prefix func uri
+    | prefix `isPrefixOf` uri = Just $ func $ drop (length prefix) uri
+    | otherwise = Nothing
+
+gsettingsUriOverride :: UriOverride
+gsettingsUriOverride = withPrefix "gsettings:" $ \path -> do
+    let [schema, key] = splitOn "/" path
+    setting <- gsettingsGet schema key
+    return $ "data:text/plain," ++ setting
+
+dataFileOverride :: UriOverride
+dataFileOverride = withPrefix "tianbar:" $ \path -> do
+    liftM ("file://" ++) $ getDataFileName path
+
+uriOverrides :: [UriOverride]
+uriOverrides = [gsettingsUriOverride, dataFileOverride]
+
+allOverrides :: UriOverride
+allOverrides = foldr mplus Nothing . flip map uriOverrides . flip ($)
+
 setupWebkitLog :: WebView -> IO ()
 setupWebkitLog wk = do
     let matcher = matchAny { matchSender = Nothing
@@ -45,15 +67,11 @@
     _ <- on wk resourceRequestStarting $ \_ _ nreq _ -> case nreq of
         Nothing -> return ()
         (Just req) -> do
-            uri_ <- networkRequestGetUri req
-            case uri_ of
+            uri <- networkRequestGetUri req
+            let override_ = uri >>= allOverrides
+            case override_ of
                 Nothing -> return ()
-                Just uri -> when (gsettingsPrefix `isPrefixOf` uri) $ do
-                    let path = drop (length gsettingsPrefix) uri
-                    let [schema, key] = splitOn "/" path
-                    setting <- gsettingsGet schema key
-                    networkRequestSetUri req $
-                        "data:text/plain," ++ setting
+                (Just override) -> override >>= networkRequestSetUri req
 
     htmlFile <- getUserConfigFile appName "index.html"
     html <- readFile htmlFile
diff --git a/src/System/Tianbar/XMonadLog.hs b/src/System/Tianbar/XMonadLog.hs
--- a/src/System/Tianbar/XMonadLog.hs
+++ b/src/System/Tianbar/XMonadLog.hs
@@ -1,3 +1,14 @@
+-- | A hook for XMonad window manager to send updates to the
+-- corresponding Tianbar widget.
+--
+-- You must include tianbar:scripts/xmonad.js in Tianbar configuration to
+-- receive the updates.
+--
+-- A "Renderer" can be used to fully customize the output. A renderer is a
+-- function receiving all the status information and returning HTML
+-- which will be displayed in the corresponding element of the status bar.
+--
+-- For convenience, a renderer returning 'Markup' can be used as well.
 module System.Tianbar.XMonadLog ( dbusLog
                                 , dbusLogWithMarkup
                                 , dbusLogWithRenderer
@@ -23,24 +34,29 @@
 import XMonad.Util.NamedWindows
 import XMonad.Util.WorkspaceCompare
 
--- | A DBus-based logger with a default pretty-print configuration
-dbusLog :: Client -> X ()
-dbusLog client = dbusLogWithMarkup client tianbarMarkup
-
 sig :: Signal
 sig = signal (fromJust $ parseObjectPath "/org/xmonad/Log")
              (fromJust $ parseInterfaceName "org.xmonad.Log")
              (fromJust $ parseMemberName "Update")
 
+-- | Workspace information.
 data WindowSpaceInfo = WindowSpaceInfo { wsTag     :: String
+                                         -- ^ workspace tag
                                        , wsCurrent :: Bool
+                                         -- ^ whether the workspace is current
                                        , wsHidden  :: Bool
+                                         -- ^ whether the workspace is hidden
                                        , wsUrgent  :: Bool
+                                         -- ^ whether the workspace has any
+                                         -- urgent windows
                                        , wsEmpty   :: Bool
+                                         -- ^ whether the workspace is empty
+                                         -- (has no windows)
                                        }
 
+-- | A function to format the status information.
 type Renderer a = String            -- ^ layout description
-               -> String            -- ^ window title
+               -> String            -- ^ active window title
                -> [WindowSpaceInfo] -- ^ workspaces
                -> [Window]          -- ^ urgent windows
                -> WindowSet         -- ^ all windows
@@ -48,6 +64,11 @@
 
 type MarkupRenderer = Renderer Markup
 
+-- | Tianbar logger with a default renderer.
+dbusLog :: Client -> X ()
+dbusLog client = dbusLogWithMarkup client tianbarMarkup
+
+-- | Tianbar logger with a renderer emitting a string.
 dbusLogWithRenderer :: Client -> Renderer String -> X ()
 dbusLogWithRenderer client renderer = do
     winset <- gets windowset
@@ -69,11 +90,13 @@
     let html = renderer ld wt (map wsinfo ws) urgents winset
     liftIO $ emit client sig { signalBody = [ toVariant html ] }
 
+-- | Tianbar logger with a Blaze renderer.
 dbusLogWithMarkup :: Client -> MarkupRenderer -> X ()
 dbusLogWithMarkup client renderer = dbusLogWithRenderer client renderer'
     where renderer' ld wt wksp urgent winset =
               renderMarkup $ renderer ld wt wksp urgent winset
 
+-- | Default Tianbar renderer.
 tianbarMarkup :: MarkupRenderer
 tianbarMarkup layout title workspaces _ _ = do
     H.span ! A.class_ (toValue "workspaces") $
diff --git a/tianbar.cabal b/tianbar.cabal
--- a/tianbar.cabal
+++ b/tianbar.cabal
@@ -1,5 +1,5 @@
 name:                tianbar
-version:             0.2.0.0
+version:             0.2.1.0
 synopsis:            A desktop bar based on WebKit
 description:
   A desktop bar using WebKit for rendering as much as possible.
@@ -12,6 +12,11 @@
 category:            System
 build-type:          Simple
 cabal-version:       >=1.10
+data-files:          README.md
+                   , scripts/location_shim.js
+                   , scripts/time.js
+                   , scripts/weather.js
+                   , scripts/xmonad.js
 
 executable tianbar
   default-language:    Haskell2010
