diff --git a/Hayoo.cabal b/Hayoo.cabal
new file mode 100644
--- /dev/null
+++ b/Hayoo.cabal
@@ -0,0 +1,181 @@
+name:          Hayoo
+version:       1.2.0
+license:       MIT
+license-file:  LICENSE
+author:        Sebastian M. Schlatt, Timo B. Huebel, Uwe Schmidt
+copyright:     Copyright (c) 2007 - 2012 Uwe Schmidt, Sebastian M. Schlatt and Timo B. Kranz
+maintainer:    Timo B. Huebel <tbh@holumbus.org>
+stability:     experimental
+category:      Text, Data
+synopsis:      The Hayoo! search engine for Haskell API search on hackage
+homepage:      http://holumbus.fh-wedel.de
+description:   The Hayoo! search engine based no the Holumbus framework provides a document indexer
+               and a crawler to build indexes over Haddock generated API documentation and packages
+               as well as a query interface for these indexes.
+cabal-version: >=1.6
+build-type:    Simple
+-- tested-with:  ghc-7.0.3
+
+extra-source-files:
+    README
+    index/hayoo/hayoo.css
+    index/hayoo/fhw.gif
+    index/hayoo/loader.gif
+    index/hayoo/favicon.ico
+    index/hayoo/content.js
+    index/hayoo/hayoo.js
+    index/hayoo/prototype.js
+    index/hayoo/ft.png
+    index/hayoo/hayoo.png
+    index/hayoo/hol.png
+    index/hayoo/holumbus.png
+    index/hayoo/minus.png
+    index/hayoo/plus.png
+    index/hayoo/opensearch.xml
+
+-- ------------------------------------------------------------
+
+executable hayooIndexer
+  main-is:      HayooIndexer.hs
+
+  other-modules:  Hayoo.PackageInfo
+                , Hayoo.URIConfig
+                , Hayoo.PackageArchive
+                , Hayoo.IndexTypes
+                , Hayoo.Haddock
+                , Hayoo.HackagePackage
+                , Hayoo.IndexConfig
+
+  build-depends:  base                   >= 4   && < 5
+                , Holumbus-Searchengine  >= 1.2 && < 2
+                , binary                 >= 0.5 && < 1
+                , bytestring             >= 0.9 && < 1
+                , bzlib                  >= 0.4 && < 1
+                , containers             >= 0.2 && < 1
+                , deepseq                >= 1.1 && < 2
+                , enummapset             >= 0   && < 1
+                , filepath               >= 1   && < 2
+                , hxt                    >= 9.1 && < 10
+                , hxt-cache              >= 9   && < 10
+                , hxt-charproperties     >= 9   && < 10
+                , hxt-curl               >= 9   && < 10
+                , hxt-http               >= 9   && < 10
+                , hxt-regex-xmlschema    >= 9   && < 10
+                , hxt-xpath              >= 9.1 && < 10
+                , network                >= 2.1 && < 3
+                , old-time               >= 1   && < 2
+                , parsec                 >= 2.1 && < 4
+                , process                >= 1   && < 2
+                , tar                    >= 0.3 && < 1
+                , zlib                   >= 0.5 && < 1 
+
+  hs-source-dirs: src
+
+  if impl( ghc >= 7 )
+    ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs -rtsopts
+  else
+    ghc-options: -threaded -Wall -funbox-strict-fields
+
+  extensions:
+
+-- ------------------------------------------------------------
+
+executable hayooSearch
+  main-is:      HayooSearch.hs
+
+  other-modules:  Hayoo.FunctionInfo
+                , Hayoo.IndexTypes
+                , Hayoo.PackageInfo
+                , Hayoo.PackageRank
+                , Hayoo.Search.Application
+                , Hayoo.Search.Common
+                , Hayoo.Search.EvalSearch
+                , Hayoo.Search.HTML
+                , Hayoo.Search.JSON
+                , Hayoo.Search.Pages.Static
+                , Hayoo.Search.Pages.Template
+                , Hayoo.Search.Parser
+                , Hayoo.Signature
+
+  build-depends:  base                      >= 4   && < 5
+                , Holumbus-Searchengine     >= 1   && < 2
+                , hack                      >= 2009.10.30
+                , hack-contrib              >= 2010.9.28
+                , hslogger                  >= 1   && < 2
+                , hxt-unicode               >= 9   && < 10
+                , json                      >= 0.4 && < 1
+                , text                      >= 0.7 && < 1
+                , xhtml-combinators         >= 0.2 && < 1
+
+  if impl( ghc >= 7.0.3 )
+    build-depends: hack-handler-simpleserver >= 0.2.0.2 && < 1
+  else
+    build-depends: hack-handler-simpleserver >= 0.2.0.1
+
+  hs-source-dirs: src
+
+  if impl( ghc >= 7 )
+    ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs -rtsopts
+  else
+    ghc-options: -threaded -Wall -funbox-strict-fields
+
+  extensions:
+
+-- ------------------------------------------------------------
+
+Flag hayooSnap
+  Description: Build Hayoo! search with Snap server
+  Default: True
+
+executable hayooSnap
+ -- if flag(hayooSnap)
+  main-is:      HayooSnap.hs
+
+  other-modules:  Hayoo.FunctionInfo
+                , Hayoo.IndexTypes
+                , Hayoo.PackageInfo
+                , Hayoo.PackageRank
+                , Hayoo.Search.Common
+                , Hayoo.Search.EvalSearch
+                , Hayoo.Search.HTML
+                , Hayoo.Search.JSON
+                , Hayoo.Search.Pages.Static
+                , Hayoo.Search.Pages.Template
+                , Hayoo.Search.Parser
+                , Hayoo.Signature
+                , Hayoo.Snap.Application
+                , Hayoo.Snap.Extension.HayooState
+                , Hayoo.Snap.Site
+
+  build-depends:  base                      >= 4     && < 5
+                , bytestring                >= 0.9.1 && < 0.10
+                , Holumbus-Searchengine     >= 1.2   && < 2
+                , hslogger                  >= 1     && < 2
+                , hxt-unicode               >= 9     && < 10
+                , json                      >= 0.4   && < 1
+                , MonadCatchIO-transformers >= 0.2.1 && < 0.3
+                , mtl                       >= 2     && < 3
+                , snap                      == 0.4.*
+                , snap-core                 == 0.4.*
+                , snap-server               == 0.4.*
+                , text                      >= 0.11  && < 1
+                , transformers              >= 0.2.2 && < 0.3
+                , xhtml-combinators         >= 0.2   && < 1
+ -- else
+  -- hayooSnap is currently only tested with ghc-7.0.3, with ghc-6.12 you will run into cabal package hell
+  -- with ghc-6.12 there is a mess with hack-handler-simpleserver, which wants old versions of network, web-encodings and text
+  -- so this is a dummy
+  -- main-is:      Dummy.hs
+
+ hs-source-dirs: src
+
+ if impl( ghc >= 7 )
+  ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs -rtsopts
+               -fno-warn-orphans -fno-warn-unused-do-bind
+ else
+  ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs
+               -fno-warn-orphans -fno-warn-unused-do-bind
+
+ extensions: TypeSynonymInstances MultiParamTypeClasses
+
+-- ------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2007 Sebastian M. Schlatt, Timo B. Hübel
+
+Permission is hereby granted, free of charge, to any person obtaining 
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
+sell copies of the Software, and to permit persons to whom the Software is 
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
+THE SOFTWARE.
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,19 @@
+The Hayoo! Search Engine
+========================
+
+The system is build with the familiar 3 steps
+
+cabal configure
+cabal build
+cabal install
+
+There are 3 executables: hayooIndexer, hayooSearch and hayooFastCGI,
+which will be installed under "~/.cabal/bin".
+
+The applications require besides various hackage packages
+the Holumbus-Searchengine package. The sources for this package
+are located in the repository "../Holumbus-Searchengine".
+
+All sources are located in subdir "src", the applications
+can be executed in subdir "index". There is a Makefile to
+build the hackage index and to start the Hayoo seach server.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,8 @@
+#!/usr/bin/runhaskell
+
+> module Main where
+
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/index/hayoo/content.js b/index/hayoo/content.js
new file mode 100644
--- /dev/null
+++ b/index/hayoo/content.js
@@ -0,0 +1,123 @@
+
+help = ' \
+<div id="result"> \
+  <div id="status">Enter some search terms above to start a search.</div> \
+\
+  <div id="helptext" class="text"> \
+\
+    <h2>Basic Usage</h2> \
+    <p> \
+      By default, Hayoo! searches for function names, module names, signatures and function descriptions. \
+      With every letter typed, Hayoo! will show the results it thinks are best matching the query as \
+      well as some suggestions on how the words from the query could be completed. Clicking one of these \
+      suggestions will replace the according word in the query. \
+    </p> \
+    <p> \
+      Hayoo! displays results as a list of functions, including full qualified module name and the function \
+      signature. Clicking the function name will lead directly to the corresponding documentation while \
+      clicking the module name will lead to the documentation of the module. Additionally, Hayoo! shows \
+      a little bit of the function description (if available) and provides a link leading directly to \
+      the source of the function (if available). \
+    </p> \
+\
+    <h2>Advanced Queries</h2> \
+    <p> \
+      If words are seperated by whitespace, Hayoo! will search for results containing both words. Instead of \
+      using whitespace, the explicit <span class="query">AND</span> operator can be used. Hayoo! also  \
+      supports <span class="query">OR</span> and <span class="query">NOT</span> operators, \
+      although the <span class="query">NOT</span> operator may only be used together with \
+      <span class="query">AND</span>, e.g. <span class="query">map NOT fold</span> or \
+      <span class="query">map AND NOT fold</span>. Operator precedence can be influenced using round parentheses. \
+      Phrases can be searched using double quotes, e.g. <span class="query">"this is a phrase"</span>. \
+    </p> \
+    <p> \
+      It is possible to restrict a search to certain packages or modules. The most simple way would be to just include \
+      the package name in the search, e.g. <span class="query">map base</span> will prefer hits from the base package. \
+      But the restriction can also be more explicit, like <span class="query">map package:base</span> or like \
+      <span class="query">map module:data.list</span>. It is also possible to specify several different modules or packages, \
+      like this: <span class="query">fold module:(data.list OR data.map)</span>. This will return all hits for fold in \
+      the module hierarchies below Data.List and Data.Map. \
+    </p> \
+    <p> \
+      Hayoo! always performs fuzzy queries. This means, it tries to find something even if the query contains \
+      spelling	errors. For example, Hayoo! will still find "fold" if "fodl" is searched. If Hayoo! \
+      detects "-&gt;" in the query string, it will only search for signatures. A signature query may consist \
+      of explicit type names as well as type variables. For example, searching for "a -&gt; b" will find \
+      signatures like "Int -&gt; Bool". \
+    <p> \
+\
+    <h2>Scope</h2> \
+    <p> \
+      Currently, Hayoo! searches all packages available on <a href="http://hackage.haskell.org">Hackage</a> \
+      and <a href="http://www.haskell.org/gtk2hs/">Gtk2Hs</a>. Additionally, any Haskell documentation generated \
+      by Haddock can be included in Hayoo!. \
+      Just send a message including an URI where the documentation can be found to \
+      <a href="mailto:hayoo@holumbus.org">hayoo@holumbus.org</a>. \
+    </p> \
+  </div> \
+</div> \
+';
+
+about = ' \
+<div id="result"> \
+  <div id="status">Enter some search terms above to start a search.</div> \
+\
+  <div id="abouttext" class="text"> \
+\
+    <h2>About Hayoo!</h2> \
+    <p> \
+      Hayoo! is a search engine specialized on <a href="http://www.haskell.org">Haskell</a> API documentation. The goal of \
+      Hayoo! is to provide an interactive, easy-to-use search interface to the documenation of various Haskell packages and \
+      libraries. Although the Hayoo! data is regularly updated, we might miss a package or library. If you think there \
+      is some documentation for Haskell modules available on the Internet which should be added to Hayoo!, \
+      just drop us a note at <a href="mailto:hayoo@holumbus.org">hayoo@holumbus.org</a> and tell us the location where \
+      we can find the documentation. \
+    </p> \
+\
+    <h2>Background</h2> \
+    <p> \
+      Hayoo! is an example application of the <a href="http://holumbus.fh-wedel.de">Holumbus</a> framework and was  \
+      heavily inspired by <a href="http://www.haskell.org/hoogle">Hoogle</a>. The Holumbus library \
+      provides the search and indexing backend for Hayoo!. Holumbus and Hayoo! have been developed by  \
+      Sebastian M. Schlatt and Timo B. Hübel at <a href="http://www.fh-wedel.de">FH Wedel University of Applied Sciences</a>.  \
+      The Holumbus framework provides the basic building blocks for creating highly customizable search engines.  \
+      To demonstrate the flexibility of the framework by a very special use case, the Hayoo! Haskell API search  \
+      was implemented using Holumbus. \
+    </p> \
+    <p> \
+      Currently, Hayoo! is still in beta stage. This means, it can become unavailable unexpectedly, as we \
+      do some maintenance or add new features. Therefore you should not yet rely on Hayoo! as primary \
+      search engine for Haskell documentation. \
+    </p> \
+\
+    <h2>Technical Information</h2> \
+    <p> \
+      Hayoo! is entirely written in Haskell, including the underlying webserver. The web interface uses the \
+      <a href="http://darcs2.fh-wedel.de/repos/janus">Janus</a> application server, written by Christian Uhlig. \
+      This server allows easy and seamless integration of Haskell code in web applications. \
+    </p> \
+\
+    <h2>Feedback</h2> \
+    <p> \
+      We would like to know what you think about Hayoo!, therefore you can reach us at \
+      <a href="mailto:hayoo@holumbus.org">hayoo@holumbus.org</a> and tell us about bugs, suggestions or \
+      anything else related to Hayoo!.\
+    </p> \
+\
+    <div id="hol"> \
+      <a href="http://holumbus.fh-wedel.de"><img class="logo" src="hayoo/hol.png" alt="Holumbus logo"/></a> \
+    </div> \
+    <div id="fhw"> \
+      <a href="http://www.fh-wedel.de"><img class="logo" src="hayoo/fhw.gif" alt="FH-Wedel logo"/></a> \
+    </div> \
+  </div> \
+</div> \
+';
+
+function showHelp() {
+  $("result").replace(help);
+}
+
+function showAbout() {
+  $("result").replace(about);
+}
diff --git a/index/hayoo/favicon.ico b/index/hayoo/favicon.ico
new file mode 100644
Binary files /dev/null and b/index/hayoo/favicon.ico differ
diff --git a/index/hayoo/fhw.gif b/index/hayoo/fhw.gif
new file mode 100644
Binary files /dev/null and b/index/hayoo/fhw.gif differ
diff --git a/index/hayoo/ft.png b/index/hayoo/ft.png
new file mode 100644
Binary files /dev/null and b/index/hayoo/ft.png differ
diff --git a/index/hayoo/hayoo.css b/index/hayoo/hayoo.css
new file mode 100644
--- /dev/null
+++ b/index/hayoo/hayoo.css
@@ -0,0 +1,491 @@
+html {
+	height:100%;
+}
+
+body {
+	font-family:Tahoma,Verdana,sans-serif;
+	margin:0px;
+	font-size:11px;
+	height:100%;
+}
+
+table, tr, td {
+	font-family:Tahoma,Verdana,sans-serif;
+	font-size:11px;
+  margin:0;
+  padding:0;
+}
+
+p {
+  margin:0;
+}
+
+a {
+	font-weight:bold;
+	color:gray;
+}
+a:link {
+	text-decoration:none;
+}
+a:hover {
+	text-decoration:none;
+	color:silver;
+}
+a:visited {
+	text-decoration:none;
+}
+a:active {
+	text-decoration:none;
+}
+
+img.logo { border: none; }
+
+span.query {
+	font-style: italic;
+}
+
+div.clear {
+  clear:both !important;
+}
+
+#throbber { 
+	vertical-align:text-bottom;
+}
+
+#copyright .author {
+	font-weight: bold;
+}
+
+#info {
+	float:right;
+	color:gray;
+	padding:5px;
+}
+
+#shutter {
+	background-color: #000000;
+	height: 100%;
+	width: 100%;
+	left: 0pt;
+	top: 0pt;
+	position: fixed;
+	opacity:0.5;
+	z-index:1000;
+}
+
+.text {
+	color:gray;
+	padding:20px;
+}
+
+.box {
+	margin-left: auto;
+	margin-right: auto;
+	margin-bottom: auto;
+	margin-top: 71px;
+	background-color: white;
+	visibility: visible;
+	opacity:1.0;
+	width: 720px;
+	height: 540px;
+	padding:10px;
+	z-index:1004;
+	color:gray;
+	text-align:left;
+	padding-left:20px;
+	padding-right:20px;
+}
+
+#examples {
+  width:80%;
+  margin-left:auto;
+  margin-right:auto;
+  background-color:#F4F4F4;
+  border:1px solid silver;
+  padding:20px;
+}
+
+.example a {
+	font-weight: normal;
+	color: #DD0000;
+	text-decoration: underline;
+}
+
+#container {
+	min-height:100%;
+	position:relative;
+}
+
+#sponsors {
+	margin-top:20px;
+	width: 100%;
+	text-align:center;
+}
+
+#fhw {
+	display: inline;
+}
+
+#hol {
+	display: inline;
+}
+
+#ft {
+	margin: 0px 50px;
+	display: inline;
+}
+
+#result {
+	padding-bottom:80px;
+}
+
+#credits {
+	border-top:1px solid silver;
+	border-bottom:1px solid silver;
+	margin-top:10px;
+	margin-bottom:10px;
+	color:gray;
+	padding-top:10px;
+	padding-bottom:5px;
+	background-color:#F4F4F4;
+	bottom:0;
+	position:absolute;
+	width:100%;
+}
+
+#powered {
+	margin-left:10px;
+	float:left;
+	vertical-align: middle;
+}
+
+#libs {
+	float: left;
+	padding-top: 10px;
+	margin-right:5px;
+}
+
+#authors {
+	text-align:right;
+	margin-right:10px;
+	float:right;
+}
+
+#feedback {
+	margin-bottom: 5px;
+}
+
+#logo {
+	float:left;
+}
+
+#status {
+	border-top:1px solid silver;
+	border-bottom:1px solid silver;
+	background-color:#F4F4F4;
+	clear:both;
+	color:gray;
+	font-weight:bold;
+	vertical-align:middle;
+	padding:2px;
+	padding-left:5px;
+}
+
+#aggregation {
+  margin:20px;
+	float:right;
+	background-color:#F4F4F4;
+	border:1px solid silver;
+}
+
+div.headline {
+  color:gray;
+  font-weight:bold;
+  margin-bottom:5px;
+}
+
+#modules {
+	padding:10px;
+}
+
+#packages {
+	padding:10px;
+}
+
+#hackage {
+  float:left;
+  margin:20px;
+	background-color:#F4F4F4;
+	border:1px solid silver;
+  width:350px;
+  padding:10px;
+}
+
+#hackage a {
+  font-weight:normal
+}
+
+#hackage a:hover {
+  text-decoration:underline;
+}
+
+#hackage div.package {
+  margin-bottom:10px;
+}
+
+#hackage a.category {
+  color:#DD0000;
+}
+
+#hackage a.name {
+  font-weight:bold;
+  color:#0700C3;
+}
+  
+#hackage div.author {
+  color:#25CA00;
+  font-weight:bold;
+  font-size:9px; 
+}
+
+#hackage span.synopsis {
+  color:gray;
+	font-weight:bold;
+}
+
+#hackage div.description {
+  font-size:9px;
+  color:gray;
+}
+
+#words {
+	padding-left: 10px;
+	padding-right: 10px;
+	margin-top: 25px;
+	margin-bottom: 5px;
+	text-align:center;
+	word-spacing: 1.5em;
+}
+
+a.rootModuleName {
+	color:#DD0000;
+  font-weight:normal;
+}
+
+.rootModuleCount {
+	color:grey;
+}
+
+a.packageLink {
+	color:#25CA00;
+  font-weight:normal;
+}
+
+.packageCount {
+	color:grey;
+}
+
+#documents {
+	padding:10px;
+}
+
+tr.details td {
+  padding-bottom:15px;
+}
+
+td.module {
+	text-align: right;
+	vertical-align: top;
+}
+
+td.description > div {
+  max-height:1.4em;
+  overflow:hidden;
+  color: gray;
+  text-align: left;
+  float:left;
+  margin-left:5px;
+  width:100%;
+}
+
+td.description > div.unfold {
+	max-height:none;
+}
+
+td.description div.description {
+  overflow-x:auto;
+  float:left;
+  max-width:80%;
+}
+
+td.description pre {
+  white-space:pre-wrap;
+}
+
+td.signature {
+  color: black;
+  margin-left:5px;
+  width:100%;
+}
+
+td.package {
+	text-align: right;
+	vertical-align: top;
+}
+
+td.name {
+  vertical-align:top;
+  width:auto;
+}
+
+span.source {
+  margin-left:5px;
+}
+
+a.module {
+	font-weight: normal;
+	color: #DD0000;
+	text-decoration: none;
+}
+
+a.module:hover {
+	text-decoration: underline;
+}
+
+a.function {
+	color: #0700C3;
+	font-weight: bold;
+}
+
+a.function:hover {
+	text-decoration: underline;
+}
+
+a.package {
+	color: #25CA00;
+	font-weight: bold;
+}
+
+a.package:hover {
+	text-decoration: underline;
+}
+
+a.source {
+	color: #F0DE00;
+	font-weight: bold;
+}
+
+a.source:hover {
+	text-decoration: underline;
+}
+
+.unfold a.toggleFold {
+  background-position:-10px;
+  background:url(minus.png);
+}
+
+a.toggleFold {
+	float:left;
+	margin-right:3px;
+	cursor:pointer;
+  display:block;
+  width:10px;
+  height:10px;
+  background:url(plus.png);
+  margin-top:2px;
+}
+
+#queryinterface {
+	height:25px;
+	padding-top:30px;
+	padding-left:300px;
+}
+
+#query {
+	color:gray;
+	margin:0px;
+}
+
+#querytext {
+	margin-top:5px;
+	border-color:silver;
+	border-width:1px;
+	border-style:solid;
+	width:300px;
+}
+
+#querybutton {
+	margin-top:5px;
+	border-color:silver;
+	border-width:1px;
+	border-style:solid;
+	background-color:#F4F4F4;
+	margin-left:5px;
+	margin-right:5px;
+}
+
+#pager {
+	text-align:center;
+	width:100%;
+	margin-top:5px;
+	margin-bottom:5px;
+}
+
+a.page {
+	padding-left:5px;
+	padding-right:5px;
+}
+
+.current {
+	font-weight:bold;
+	color:silver;
+	padding-left:5px;
+	padding-right:5px;
+}
+
+a.next {
+	padding-left:5px;
+	padding-right:5px;
+}
+
+a.previous {
+	padding-left:5px;
+	padding-right:5px;
+}
+
+a.cloud {
+	font-weight:normal;
+}
+
+.cloud1 { font-size:10px; }
+.cloud2 { font-size:13px; }
+.cloud3 { font-size:16px; }
+.cloud4 { font-size:19px; }
+.cloud5 { font-size:22px; }
+.cloud6 { font-size:25px; }
+.cloud7 { font-size:28px; }
+.cloud8 { font-size:31px; }
+.cloud9 { font-size:33px; }
+
+.link { color:navy; }
+.link:hover { 
+	color:navy; 
+	text-decoration:underline;
+}
+
+.title { font-size:10pt; }
+.contexts { color:gray; }
+.context { font-weight:bold; }
+.uri { color:green; }
+.declaration { font-style: italic; }
+
+div.stats {
+  font-size:80%;
+  font-style:italic;
+}
+
+.example p {
+  margin:10px 0px;
+}
+
+
diff --git a/index/hayoo/hayoo.js b/index/hayoo/hayoo.js
new file mode 100644
--- /dev/null
+++ b/index/hayoo/hayoo.js
@@ -0,0 +1,136 @@
+/*
+
+  The Hayoo! AJAX interface
+
+  Copyright  : Copyright (C) 2008 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Version    : 0.2
+
+*/
+
+prevInput = "";
+lastLocation = window.location.hash;
+refreshRequired = false;
+
+Event.observe(window, 'load', focusQuery);
+
+function focusQuery() {
+  $("querytext").focus();
+}
+
+/*
+Event.observe(window, 'load', checkForInitialQuery);
+
+function checkForInitialQuery() {
+  var argument = window.location.search.gsub(/\+/, '%20').toQueryParams()['query'];
+
+  if (argument) {
+    $("querytext").value = argument;
+    processQuery(0);
+  }
+}
+*/
+
+function checkForQuery () {
+  if (lastLocation != window.location.hash) {
+    lastLocation = window.location.hash;
+    var prev = window.location.hash.split(":");
+
+    var start = prev[0].substr(1);
+    prev.shift();
+    var query = decodeURIComponent(prev.join(":"));
+
+    $("querytext").value = query;
+    processQuery(parseInt(start));
+  }
+  else {
+    window.setTimeout(checkForQuery, 200);
+  }
+}
+
+function tryProcessQuery () {
+  var query = $("querytext").value;
+  window.setTimeout('checkProcessQuery(\'' + encodeURIComponent(query) + '\')', 300);
+}
+
+function checkProcessQuery (query) {
+  if ((query != prevInput) && (query == encodeURIComponent($("querytext").value))) {
+    prevInput = query;
+    processQuery(0);
+  }
+}
+
+function forceProcessQuery () {
+  processQuery(0);
+  return false;
+}
+
+function processQuery (start) {
+  var query = $("querytext").value;
+  if (query.length > 0) {
+    $("throbber").show();
+    refreshRequired = true;
+    new Ajax.Request("hayoo.html?query=" + encodeURIComponent(query) + "&start=" + start + "&static=False",
+      {
+        method:'get',
+        onSuccess: function(transport) {
+          lastXMLResult = transport.responseXML;
+          lastTXTResult = transport.responseText;
+          lastQuery = query;
+          window.location.hash = start + ":" + encodeURIComponent(query);
+          lastLocation = window.location.hash;
+          displayResult(transport.responseText, query);
+          checkForQuery();
+          piwikTracker.trackPageView("hayoo.html?query=" + encodeURIComponent(query));
+        },
+        onFailure: function() {
+          var resultError = "<div id=\"result\"><div id=\"status\">Error: Could not execute query.</div><div id=\"words\">&nbsp;</div><div id=\"documents\">&nbsp;</div><div id=\"pager\">&nbsp;</div></div>";
+          $("result").replace(resultError);
+        }
+      }
+    );
+  }
+}
+
+function displayResult (result, query) {
+  if (refreshRequired) {
+    refreshRequired = false;
+    $("result").replace(result);
+    $("querytext").defaultValue = query;
+  }
+  $("throbber").hide();
+}
+
+function replaceInQuery (needle, substitute) {
+  if ($("querytext").value == "") {
+    $("querytext").value = substitute;
+  } else {
+    $("querytext").value = $("querytext").value.gsub(needle, substitute);
+  }
+  processQuery(0);
+}
+
+function addToQuery (addition) {
+  $("querytext").value = $("querytext").value + ' ' + addition;
+  processQuery(0);
+}
+
+function toggleFold(node) {
+
+  if ($(node).parentNode.hasClassName('unfold')) {
+    $(node).parentNode.removeClassName('unfold');
+  } else {
+    $(node).parentNode.addClassName('unfold');
+  }
+
+  return false;
+}
+
+function showPage (page) {
+  if ($("querytext").value == "") {
+    $("querytext").value = lastQuery;
+  }
+  processQuery (page);
+}
diff --git a/index/hayoo/hayoo.png b/index/hayoo/hayoo.png
new file mode 100644
Binary files /dev/null and b/index/hayoo/hayoo.png differ
diff --git a/index/hayoo/hol.png b/index/hayoo/hol.png
new file mode 100644
Binary files /dev/null and b/index/hayoo/hol.png differ
diff --git a/index/hayoo/holumbus.png b/index/hayoo/holumbus.png
new file mode 100644
Binary files /dev/null and b/index/hayoo/holumbus.png differ
diff --git a/index/hayoo/loader.gif b/index/hayoo/loader.gif
new file mode 100644
Binary files /dev/null and b/index/hayoo/loader.gif differ
diff --git a/index/hayoo/minus.png b/index/hayoo/minus.png
new file mode 100644
Binary files /dev/null and b/index/hayoo/minus.png differ
diff --git a/index/hayoo/opensearch.xml b/index/hayoo/opensearch.xml
new file mode 100644
--- /dev/null
+++ b/index/hayoo/opensearch.xml
@@ -0,0 +1,10 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+  <ShortName>Hayoo!</ShortName>
+  <Description>Haykell API Search</Description>
+  <InputEncoding>UTF-8</InputEncoding>
+  <Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAd2wwAH3MMAB%2FzDAAfrwwAHgf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAd%2FwwAHyMMAB9DDAAfNwwAHecMAB07DAAf%2FwwAHa%2F%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAMMAB1XDAAf%2FwwAHav%2F%2F%2FwDDAAdIwwAH%2F8MAB1QAAN0BAADdwQAA3aL%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAcswwAH%2F8MABy3%2F%2F%2F8AwwAHSMMAB%2F%2FDAAdGAADdBwAA3ecAAN3C%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8AwwAHXMMAB%2FbDAAcD%2F%2F%2F%2FAMMAB0jDAAf%2FwwAHOP%2F%2F%2FwAAAN0IAADdCf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAMMAB4zDAAfH%2F%2F%2F%2FAMMAByLDAAeEwwAH%2F8MABy7%2F%2F%2F8A%2F%2F%2F%2FAAAA3Uz%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAfBwwAH58MAB7fDAAeVwwAHe8MAB%2F%2FDAAdq%2F%2F%2F%2FAP%2F%2F%2FwAAAN3AAADdCf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAc8wwAH%2F8MAB1v%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAfgwwAHv%2F%2F%2F%2FwD%2F%2F%2F8AAADdsQAA3U3%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAcCwwAHzcMAB9DDAAcB%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8AwwAHi8MAB%2F3DAAcY%2F%2F%2F%2FAAAA3ZcAAN2c%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8AwwAHZMMAB%2F%2FDAAdI%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAMMABzXDAAf%2FwwAHav%2F%2F%2FwAAAN19AADd6QAA3QL%2F%2F%2F8AwwAHD8MAB%2BnDAAe%2F%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAcUwwAH%2F8MAB8P%2F%2F%2F8AAADdZAAA3f8AAN05%2F%2F%2F%2FAMMAB5XDAAf%2FwwAHWf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAd9wwAHysMAB%2BDDAAfgwwAHszcAoF8AAN3%2FAADdiMMAB63DAAfQwwAH0MMAB8LDAAd3%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwAAAN0pAADd%2FgAA3a7%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAAAA3UUAAN0d%2F%2F8AAP%2F%2FAAD%2FDwAA8b8AAPuzAAD7swAA%2B78AAPM%2FAADwuwAA95sAAOeZAADv3QAAz80AAJ%2BEAAAP%2FAAA%2F%2F8AAA%3D%3D</Image>
+  <Url type="text/html" method="GET" template="http://www.holumbus.org/hayoo/hayoo.html">
+    <Param name="query" value="{searchTerms}"/>
+  </Url>
+<moz:SearchForm>http://www.holumbus.org/hayoo/hayoo.html</moz:SearchForm>
+</OpenSearchDescription>
diff --git a/index/hayoo/plus.png b/index/hayoo/plus.png
new file mode 100644
Binary files /dev/null and b/index/hayoo/plus.png differ
diff --git a/index/hayoo/prototype.js b/index/hayoo/prototype.js
new file mode 100644
--- /dev/null
+++ b/index/hayoo/prototype.js
@@ -0,0 +1,4221 @@
+/*  Prototype JavaScript framework, version 1.6.0.2
+ *  (c) 2005-2008 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.2',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = function(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  };
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).select("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor && nextAncestor.sourceIndex)
+       return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if ('outerHTML' in document.createElement('div')) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();
diff --git a/src/Hayoo/FunctionInfo.hs b/src/Hayoo/FunctionInfo.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/FunctionInfo.hs
@@ -0,0 +1,62 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.FunctionInfo
+    ( FunctionInfo(..)
+    , mkFunctionInfo
+    )
+where
+import           Control.DeepSeq
+import           Control.Monad                  ( liftM5 )
+
+import           Data.Binary                    ( Binary(..) )
+import qualified Data.Binary                    as B
+
+import           Text.XML.HXT.Core
+
+-- ------------------------------------------------------------
+
+-- | Additional information about a function.
+
+data FunctionInfo
+    = FunctionInfo 
+      { moduleName  :: String               -- ^ The name of the module containing the function, e.g. Data.Map
+      , signature   :: String               -- ^ The full signature of the function, e.g. Ord a => a -> Int -> Bool
+      , package     :: String               -- ^ The name of the package containing the module, e.g. containers
+      , sourceURI   :: String               -- ^ An optional URI to the online source of the function.
+      , fctDescr    :: String               -- ^ The haddock description of a type or function, maybe shortened for space efficiency
+      } 
+    deriving (Show, Eq)
+
+mkFunctionInfo                  :: String -> String -> String -> String -> String -> FunctionInfo
+mkFunctionInfo                  = FunctionInfo
+
+instance XmlPickler FunctionInfo where
+    xpickle                     = xpWrap (fromTuple, toTuple) xpFunction
+        where
+        fromTuple (m, s, p, r, d)
+                                = FunctionInfo m s p r d
+        toTuple (FunctionInfo m s p r d)
+                                = (m, s, p, r, d)
+
+        xpFunction              = xp5Tuple xpModule xpSignature xpPackage xpSource xpDescr
+            where                                                       -- We are inside a doc-element, and everything is stored as attribute.
+            xpModule            = xpAttr "module"    xpText0
+            xpSignature         = xpAttr "signature" xpText0
+            xpPackage           = xpAttr "package"   xpText0
+            xpSource            = xpAttr "source"    xpText0
+            xpDescr             = xpAttr "descr"     xpText0
+
+instance NFData FunctionInfo where
+  rnf (FunctionInfo m s p r d)  = rnf m `seq` rnf s `seq` rnf p `seq` rnf r `seq` rnf d
+
+instance B.Binary FunctionInfo where
+    put (FunctionInfo m s p r d)
+                                = put m >> put s >> put p >> put r >> put d
+    get                         = do
+                                  r <- liftM5 FunctionInfo get get get get get
+                                  rnf r `seq`
+                                      return r
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/HackagePackage.hs b/src/Hayoo/HackagePackage.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/HackagePackage.hs
@@ -0,0 +1,153 @@
+module Hayoo.HackagePackage
+where
+
+import           Data.List
+
+import           Hayoo.URIConfig
+import           Hayoo.PackageInfo
+
+import           Holumbus.Crawler.Html
+
+import           Text.XML.HXT.Core
+  
+import           Text.Regex.XMLSchema.String
+
+-- ------------------------------------------------------------
+
+hayooGetPkgInfo                 :: IOSArrow XmlTree PackageInfo
+hayooGetPkgInfo                 = fromLA $
+                                  ( getPkgNameAndVersion
+                                    &&&
+                                    getPkgDependencies
+                                    &&&
+                                    getPkgAuthor
+                                    &&&
+                                    getPkgMaintainer
+                                    &&&
+                                    getPkgCategory
+                                    &&&
+                                    getPkgHomepage
+                                    &&&
+                                    getPkgSynopsis
+                                    &&&
+                                    getPkgDescr
+                                  )
+                                  >>^
+                                  (\ ((x1, x2), (x3, (x4, (x5, (x6, (x7, (x8, x9))))))) -> mkPackageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9)
+
+hayooGetPkgTitle                :: IOSArrow XmlTree String
+hayooGetPkgTitle                = fromLA $
+                                  getPkgName
+
+-- ------------------------------------------------------------
+
+getPkgName                      :: LA XmlTree String
+getPkgName                      = getPkgNameAndVersion >>^ fst
+
+getPkgNameAndVersion            :: LA XmlTree (String, String)
+getPkgNameAndVersion            = getHtmlTitle
+                                  >>^
+                                  ( ( words >>> drop 1 >>> take 1 >>> unwords )
+                                    >>>
+                                    ( sed (const "") packageVersion''
+                                      &&&
+                                      ( tokenize packageVersion'' >>> reverse >>> take 1 >>> concat >>> drop 1)
+                                    )
+                                  )
+
+getPkgDependencies              :: LA XmlTree [String]
+getPkgDependencies              = getProperty "Dependencies"
+                                  >>>
+                                  listA
+                                  ( getChildren
+                                    >>>
+                                    hasName "a"
+                                    >>>
+                                    getChildren
+                                    >>>
+                                    getText
+                                    >>>
+                                    this -- checkPackageName
+                                  )
+                                  >>>
+                                  arr (sort >>> nub)
+
+getPkgAuthor                    :: LA XmlTree String
+getPkgAuthor                    = getAllText $ getProperty "Author(s)?"
+
+getPkgMaintainer                :: LA XmlTree String
+getPkgMaintainer                = getAllText $ getProperty "Maintainer(s)?"
+
+getPkgCategory                  :: LA XmlTree String
+getPkgCategory                  = getAllText $ getProperty "Category"
+
+getPkgHomepage                  :: LA XmlTree String
+getPkgHomepage                  = getAllText $ getProperty "Home page"
+
+getPkgSynopsis                  :: LA XmlTree String
+getPkgSynopsis                  = ( getAllText
+                                    ( getByPath ["html","body"]
+                                      />
+                                      isElemWithAttr "div" "id" (== "package-header")
+                                      />
+                                      isElemWithAttr "p" "class" (== "caption")
+                                    )
+                                  )
+                                  >>^
+                                  ( dropWhile (/= ':') >>> drop 1 >>> dropWhile (== ' '))       -- remove package name
+
+getPkgDescr                     :: LA XmlTree String
+getPkgDescr                     = getAllText                    -- take all stuff between "h1" and next "h2" element in content
+                                  ( ( getByPath ["html","body"]
+                                      />
+                                      isElemWithAttr "div" "id" (== "content")
+                                    )
+                                    >>>
+                                    listA getChildren
+                                    >>>
+                                    spanA (neg $ hasName "h2")
+                                    >>>
+                                    arr fst
+                                    >>>
+                                    unlistA
+                                    >>>
+                                    (none `when` hasName "h1")
+                                  )
+
+-- ------------------------------------------------------------
+
+preparePkg                      :: IOSArrow XmlTree XmlTree
+preparePkg                      = fromLA $
+                                  isHackagePackage
+
+isHackagePackage                :: LA XmlTree XmlTree
+isHackagePackage                = hasAttrValue transferURI (match $ hackagePackages ++ fileName)
+
+-- ------------------------------------------------------------
+
+getProperties           :: LA XmlTree XmlTree
+getProperties           = single (deep (hasName "table"))               -- there should be only a single table
+                                                                        -- old package layout:
+                                                                        -- deep ( isElemWithAttr "table" "class" (== "properties") )
+                          />
+                          hasName "tr"
+
+getProperty             :: String -> LA XmlTree XmlTree
+getProperty kw          = getProperties
+                          >>>
+                          ( ( getChildren
+                              >>>
+                              hasName "th"
+                              >>>
+                              ( getChildren >>. take 1 )
+                              >>>
+                              hasText (match kw)
+                            )
+                            `guards`
+                            ( getChildren
+                              >>>
+                              hasName "td"
+                            )
+                          )
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/Haddock.hs b/src/Hayoo/Haddock.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Haddock.hs
@@ -0,0 +1,872 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.Haddock
+    ( hayooGetFctInfo
+    , hayooGetTitle
+    , hayooGetDescr
+    , prepareHaddock
+    )
+where
+
+import Data.List
+import Data.Maybe
+
+import Hayoo.URIConfig
+import Hayoo.FunctionInfo
+import Hayoo.Signature
+
+import Holumbus.Crawler.Html
+import Holumbus.Utility
+
+import Network.URI                      ( unEscapeString )
+
+import Text.Regex.XMLSchema.String      ( match
+                                        , tokenize
+                                        )
+
+import Text.XML.HXT.Core     
+import Text.XML.HXT.Arrow.XmlRegex
+import Text.XML.HXT.XPath
+
+-- ------------------------------------------------------------
+
+hayooGetFctInfo                 :: IOSArrow XmlTree FunctionInfo
+hayooGetFctInfo                 = fromLA $
+                                  ( getAttrValue "module"
+                                    &&&
+                                    getAttrValue "signature"
+                                    &&&
+                                    getAttrValue "package"
+                                    &&&
+                                    getAttrValue "source"
+                                    &&&
+                                    xshow
+                                    ( hayooGetDescr
+                                      >>>
+                                      getChildren
+                                      >>>
+                                      editDescrMarkup
+                                    )
+                                  )
+                                  >>^
+                                  (\ (m, (s, (p, (r, d)))) -> mkFunctionInfo m s p r d)
+
+hayooGetTitle                   :: IOSArrow XmlTree String
+hayooGetTitle                   = fromLA $
+                                  getAttrValue "title"
+
+hayooGetDescr                   :: LA XmlTree XmlTree
+hayooGetDescr                   = ifA version28
+                                  ( getChildren                                 -- 2.8
+                                    >>>
+                                    divWithClass (== "top")
+                                    >>>
+                                    firstChildWithClass "div" "doc"
+                                  )
+                                  ( deep (trtdWithClass (== "doc")) )           -- 2.6
+
+editDescrMarkup                 :: LA XmlTree XmlTree
+editDescrMarkup                 = processTopDown
+                                  ( remHref `when` hasName "a" )
+    where
+    remHref                     = processAttrl (none `when` hasName "href")
+
+version28                       :: LA XmlTree XmlTree
+version28                       = hasAttrValue "version" (== "2.8")
+
+-- ------------------------------------------------------------
+
+prepareHaddock                  :: IOSArrow XmlTree XmlTree
+prepareHaddock                  = ( traceMsg 1 "prepareHaddock: try version 2.8"
+                                    >>>
+                                    prepareHaddock28
+                                  )
+                                  `orElse`
+                                  ( traceMsg 1 "prepareHaddock: try version 2.6"
+                                    >>>
+                                    prepareHaddock26
+                                  )
+                                  `orElse`
+                                  ( traceMsg 1 "prepareHaddock: no haddock" )
+
+-- ------------------------------------------------------------
+
+isHaddock28                     :: LA XmlTree XmlTree
+isHaddock28                     = getPath "html/body/div/p"
+                                  >>>
+                                  ( hasAElemWithHaddock
+                                    `guards`
+                                    hasVersionGE28
+                                  )
+    where
+    hasAElemWithHaddock         = this
+                                  /> hasName "a"
+                                  /> hasText (== "Haddock")
+    hasVersionGE28              = this
+                                  /> hasText matchVersionGE28
+
+matchVersionGE28                :: String -> Bool
+matchVersionGE28                = match ".* [2-9][.]([1-9][0-9]+|[8-9])([.][0-9]+)*"
+
+prepareHaddock28                :: IOSArrow XmlTree XmlTree
+prepareHaddock28                = fromLA $
+                                  seqA
+                                  [ this
+                                  , ( isHaddock28 `guards` this )
+                                  , addPackageAttr
+                                  , addModuleAttr
+                                  , splitHaddock28
+                                  ]
+                                  -- >>> withTraceLevel 4 (traceDoc "result of splitHaddock28") -- just for dev.
+
+splitHaddock28                  :: LA XmlTree XmlTree
+splitHaddock28                  = mkVirtualDoc28 $< this
+
+mkVirtualDoc28                  :: XmlTree -> LA XmlTree XmlTree
+mkVirtualDoc28 rt               = (getModule <+> getDecls)
+                                  >>>
+                                  mkDoc
+    where
+    mkDoc                       = ( root [] []
+                                    += attr "title"     (theTitle     >>> mkText)
+                                    += attr "module"    (theModule    >>> mkText)
+                                    += attr "package"   (thePackage   >>> mkText)
+                                    += attr "signature" (theSignature >>> mkText)
+                                    += attr "source"    (theSourceURI >>> mkText)
+                                    += sattr "version"  "2.8"
+                                    += attr transferURI ( ( (theURI &&& theLinkPrefix &&& theTitle)
+                                                            >>^
+                                                            (\ (u, (h, t)) -> u ++ h ++ t)
+                                                          )
+                                                          >>>
+                                                          mkText
+                                                        )
+                                    += removeSourceLinks
+                                  )
+
+    getModule                   =  mkModuleDecl
+                                   ( ( eelem "a"
+                                       += sattr "class" "def"
+                                       += theModuleName
+                                     )
+                                     <+>
+                                     theSourceLink
+                                   )
+                                   theModuleDoc
+        where
+        theModuleName           = xshow
+                                  ( getById "module-header"                     -- single (deep (isElemWithAttr "div" "id" (== "module-header")))
+                                    /> pWithClass (== "caption")
+                                    /> isText
+                                  )
+                                  >>>
+                                  arr (tokenize "[^.]+" >>> last)                -- A will be the name of module C.B.A
+                                  >>>
+                                  mkText
+
+        theSourceLink           = getById "package-header"                      -- single (deep (isElemWithAttr "div" "id" (== "package-header")))
+                                  /> getById "page-menu"                        -- isElemWithAttr "ul" "id" (== "page-menu")
+                                  /> hasName "li"
+                                  /> hasName "a"
+                                  >>> ( (getChildren >>> hasText (== "Source"))
+                                        `guards`
+                                        ( this += sattr "class" "link" )
+                                      )
+
+        theModuleDoc            = getById "description"                         -- single (deep (isElemWithAttr "div" "id" (== "description")))
+                                  />
+                                  divWithClass (== "doc")
+
+    getDecls                    = getById "interface"                           -- this //> isElemWithAttr "div" "id" (== "interface")
+                                  />  divWithClass (== "top")
+                                  >>> choiceA
+                                      [ isDataTypeNewtypeDecl
+                                                       :-> ( processTypeDecl
+                                                             <+>
+                                                             ( processConstructors $< getSrcLnk ) -- the data sourrce link is propagated
+                                                           )                                      -- to the constructors and fields
+                                      , isClassDecl    :-> ( processClassDecl
+                                                             <+>
+                                                             processMethods
+                                                           )
+                                      , isFunctionDecl :-> processFunctionDiv
+                                      , this           :-> none
+                                      ]
+
+    getSrcLnk                   = ( first_p_src
+                                    >>>
+                                    firstChildWithClass "a" "link"
+                                  )
+                                  `orElse`
+                                  txt ""
+
+    isFunctionDecl              = first_p_src
+                                  >>>
+                                  firstChild (aWithClass (== "def")
+                                              >>>
+                                              hasAttr "name"
+                                             )
+
+    isClassDecl                 = isTDecl (== "class")
+
+    isDataTypeNewtypeDecl       = isTDecl (`elem` ["data", "type", "newtype"])
+
+    isTDecl p                   = first_p_src                                   -- check the first p element found
+                                  >>>
+                                  firstChildWithClass "span" "keyword"          -- check the first keyword found
+                                  />
+                                  hasText p
+
+    processFunctionDiv         = (mkSingleDiv $< splitMultiDiv)
+                                 += sattr "type" "function"
+        where
+          mkSingleDiv ts        = replaceChildren (constL ts)
+          splitMultiDiv         = listA getChildren
+                                  >>> arr (head &&& tail)
+                                  >>> first processFunctionSig
+                                  >>> arr (uncurry (:))
+
+    processFunctionSig          = mkSingleFct $< splitMultiFct
+        where
+          mkSingleFct ts        = replaceChildren (constL ts)
+          splitMultiFct         = listA getChildren
+                                  >>> spanA (neg $ hasText ((== "::") . stringTrim))
+                                  >>> first ( unlistA
+                                              >>> aWithClass (== "def")
+                                              >>> hasAttr "name"
+                                            ) 
+                                  >>> arr (uncurry (:))
+
+    processTypeDecl             = this +=  attr "type" theType
+
+    processConstructors srcLnk  = this
+                                  />  divWithClass (words >>> ("constructors" `elem`))
+                                  />  hasName "table"
+                                  />  hasName "tr"
+                                  >>> ( (isConstrRow `guards` theConstructors srcLnk)
+                                        <+>
+                                        processConstrFields srcLnk
+                                      )
+    isConstrRow                 = matchRegexA ( mkSeq
+                                                (mkPrimA $ tdWithClass (== "src"))
+                                                (mkPrimA $ tdWithClass (words >>> ("doc" `elem`)))
+                                              )
+                                  getChildren
+                                  >>>
+                                  unlistA
+
+    processConstrFields srcLnk  = getChildren
+                                  >>>
+                                  hasName "td"
+                                  />
+                                  divWithClass (words >>> ("fields" `elem`))
+                                  />
+                                  hasName "dl"
+                                  >>>
+                                  scanRegexA 
+                                      ( mkSeq (mkPrimA $ hasName "dt") (mkPrimA $ hasName "dd"))
+                                      getChildren
+                                  >>>
+                                  theConstrFields srcLnk
+
+    processClassDecl            = ( this += sattr "type" "class" )
+                                  >>>
+                                  processChildren ( pWithClass   (== "src")
+                                                    <+>
+                                                    divWithClass (== "doc")
+                                                  )
+
+    processMethods              = getChildren
+                                  >>>
+                                  divWithClass (words >>> ("methods" `elem`))
+                                  >>>
+                                  scanRegexA
+                                    ( mkSeq
+                                      (        mkPrimA $ hasName "p")
+                                      (mkOpt $ mkPrimA $ hasName "div")
+                                    )
+                                    ( getChildren
+                                      >>>
+                                      ( pWithClass   (== "src")
+                                        <+>
+                                        divWithClass (words >>> ("doc" `elem`))
+                                      )
+                                    )
+                                  >>>
+                                  theMethods
+
+    theType                     = single (getPath "p/span" /> isText)
+
+    theTitle                    = xshow ( first_p_src
+                                          >>>
+                                          firstChildWithClass "a" "def"
+                                          />
+                                          isText
+                                        )
+                                  >>^
+                                  unEscapeString
+
+    theConstructors srcLnk      = mkFctDecl
+                                  ( ( getChildren
+                                      >>>
+                                      tdWithClass (== "src")
+                                      >>>
+                                      getChildren
+                                    )
+                                    <+>
+                                    constA srcLnk
+                                  )
+                                  ( getChildren
+                                    >>>
+                                    tdWithClass (== "doc")
+                                    >>>
+                                    getChildren
+                                  )
+
+    theConstrFields srcLnk      = mkFctDecl
+                                  ( (unlistA >>> hasName "dt" >>> getChildren)
+                                    <+>
+                                    constA srcLnk
+                                  )
+                                  ( unlistA >>> hasName "dd" >>> getChildren )
+
+    theMethods                  = mkDecl0 "function" ( this >>> unlistA )
+
+    theLinkPrefix               = ( first_p_src
+                                    >>>
+                                    firstChildWithClass "a" "def"
+                                    >>>
+                                    getAttrValue "name"
+                                    >>^
+                                    (take 2 >>> ('#' :))
+                                  )
+                                  `withDefault` "#v:"
+
+    theSignature                = ( ifA
+                                    ( hasAttrValue "type" (`elem` ["function"]) )
+                                    ( xshow ( ( single ( this /> pWithClass (== "src") )
+                                                <+>
+                                                theSubArguments         -- fancy arguments
+                                              )
+                                              >>>
+                                              removeSourceLinks
+                                              >>>
+                                              deep isText
+                                            )
+                                    )
+                                    ( getAttrValue "type" )
+                                  )
+                                  >>^
+                                  getSignature
+
+    theSubArguments             = getChildren
+                                  >>>
+                                  divWithClass (words >>> ("arguments" `elem`))
+                                  />
+                                  hasName "table"
+                                  />
+                                  hasName "tr"
+                                  />
+                                  tdWithClass (== "src")
+                                  >>>
+                                  getChildren
+                                  
+    theSourceURI                = ( first_p_src
+                                    >>>
+                                    firstChildWithClass "a" "link"
+                                    >>>
+                                    getAttrValue       "href"
+                                  )
+                                  `withDefault` ""
+
+    theModule                   = constA rt >>> getAttrValue "module"
+    thePackage                  = constA rt >>> getAttrValue "package"
+    theURI                      = constA rt >>> getAttrValue transferURI
+
+-- ------------------------------------------------------------
+
+isHaddock26                     :: LA XmlTree XmlTree
+isHaddock26                     = getPath "html/body/table/tr"
+                                  /> tdWithClass (== "botbar")
+                                  /> hasName "a"
+                                  /> hasText (== "Haddock")
+
+prepareHaddock26                :: IOSArrow XmlTree XmlTree
+prepareHaddock26                = process
+                                  [ this
+                                  , ( isHaddock26 `guards` this )
+                                  , addPackageAttr
+                                  , addModuleAttr
+                                  , processClasses                              -- .4
+                                  , topdeclToDecl                               -- .5
+                                  , removeDataDocumentation                     -- .6
+                                  , processDataTypeAndNewtypeDeclarations       -- .7
+                                  , processCrazySignatures
+                                  , splitHaddock26
+                                  ]
+    where
+    process                     = seqA . zipWith phase [(0::Int)..]
+    phase _i f                  = fromLA f
+                                  -- >>>
+                                  -- traceDoc ("prepare haddock-2.6: step " ++ show i)
+
+splitHaddock26                  :: LA XmlTree XmlTree
+splitHaddock26                  = mkVirtualDoc26 $< this
+
+mkVirtualDoc26                  :: XmlTree -> LA XmlTree XmlTree
+mkVirtualDoc26 rt               = getDecls
+                                  >>>
+                                  ( root [] []
+                                    += attr "title"     (theTitle     >>> mkText)
+                                    += attr "module"    (theModule    >>> mkText)
+                                    += attr "package"   (thePackage   >>> mkText)
+                                    += attr "signature" (theSignature >>> mkText)
+                                    += attr "source"    (theSourceURI >>> mkText)
+                                    += sattr "version"  "2.6"
+                                    += attr transferURI ( ( (theURI &&& theLinkPrefix &&& theTitle)
+                                                            >>^
+                                                            (\ (u, (h, t)) -> u ++ h ++ t)
+                                                          )
+                                                          >>>
+                                                          mkText
+                                                        )
+                                    += removeSourceLinks
+                                  )
+    where
+    getDecls                    = deep ( isDecl' >>> hasAttr "id" )
+
+    isDecl'                     = trWithClass (== "decl")
+
+    theTitle                    = ( listA (isDecl' >>> getAttrValue "id") >>. concat )
+                                  >>^
+                                  unEscapeString
+
+    theSignature                = xshow ( removeSourceLinks
+                                          >>>
+                                          deep (tdWithClass (== "decl"))
+                                          >>>
+                                          deep isText
+                                        )
+                                  >>^
+                                  getSignature
+
+    theLinkPrefix               = theSignature
+                                  >>^
+                                  ( words
+                                    >>>
+                                    take 1
+                                    >>>
+                                    concat
+                                    >>>
+                                    (\ s -> if s `elem` ["data", "type", "newtype"]
+                                            then "#t:"
+                                            else "#v:"
+                                    )
+                                  )
+    theSourceURI                = ( single
+                                    ( ( deep ( ( aWithHref ("src/" `isPrefixOf`)
+                                                 />
+                                                 hasText (== "Source")
+                                               )
+                                               `guards` this
+                                             )
+                                        >>>
+                                        getAttrValue0 "href"
+                                      )
+                                      &&&
+                                      theURI
+                                    )
+                                    >>^ (uncurry expandURIString >>> fromMaybe "")
+                                  )
+                                  `withDefault` ""
+
+    theModule                   = constA rt >>> getAttrValue "module"
+    thePackage                  = constA rt >>> getAttrValue "package"
+    theURI                      = constA rt >>> getAttrValue transferURI
+
+-- ------------------------------------------------------------
+ 
+-- | Transform classes so that the methods are wrapped into the same html as normal functions
+
+processClasses                  :: LA XmlTree XmlTree
+processClasses                  = processTopDown
+                                  ( processClassMethods
+                                    `when`
+                                    ( getClassPart "section4"
+                                      /> hasText (== "Methods")
+                                    )
+                                  )
+    where   
+    processClassMethods         = getClassPart "body"
+                                  /> hasName "table"
+                                  /> hasName "tr"
+                                -- getXPathTrees "/tr/td[@class='body']/table/tr/td[@class='body']/table/tr" 
+
+-- ------------------------------------------------------------
+
+-- | Removes Source Links from the XmlTree. A Source Link can be identified by the text of an "a" 
+--   node but to be more precise it is also checked whether the href-attribute starts with "src".
+--   During the tree transformation it might happen, that source links with empty href attributes 
+--   are constructed so empty href attributes are also searched and removed if the text of the "a"
+--   node is "Source"
+
+removeSourceLinks               :: LA XmlTree XmlTree
+removeSourceLinks               = processTopDown
+                                  ( none
+                                    `when` 
+                                    ( aWithHref (\a -> null a || "src/" `isPrefixOf` a)
+                                      />
+                                      hasText (== "Source")
+                                    )
+                                  )      
+
+-- ------------------------------------------------------------
+
+-- | As Haddock can generate Documentation pages with links to source files and without these links
+--   there are two different types of declaration table datas. To make the indexing easier, the
+--   table datas with source links are transformed to look like those without (they differ 
+--   in the css class of the table data and the ones with the source links contain another table).
+
+topdeclToDecl                   :: LA XmlTree XmlTree
+topdeclToDecl                   = processTopDownUntil
+                                  ( isElemWithAttr "table" "class" (== "declbar")
+                                   `guards`
+                                   ( getChildren >>> getChildren >>> getChildren )
+                                  )
+                                  >>>
+                                  processTopDownUntil
+                                  ( tdWithClass (== "topdecl")
+                                    `guards`
+                                    mkelem "td" [ sattr "class" "decl"] [ getChildren ]
+                                  ) 
+
+-- ------------------------------------------------------------
+
+removeDataDocumentation         :: LA XmlTree XmlTree
+removeDataDocumentation         = processTopDown 
+                                  ( none
+                                    `when`
+                                    ( getClassPart "section4"
+                                      /> hasText (\a -> a == "Constructors"
+                                                        || "Instances" `isSuffixOf` a
+                                                 )
+                                    )
+                                  )
+
+-- ------------------------------------------------------------
+
+processDataTypeAndNewtypeDeclarations :: LA XmlTree XmlTree
+processDataTypeAndNewtypeDeclarations 
+                                = processTopDownUntil
+                                  ( (    tdWithClass   (=="decl")
+                                      /> spanWithClass (=="keyword")
+                                      /> hasText (`elem` ["data", "type", "newtype", "class"]) 
+                                    )
+                                    `guards`
+                                    ( mkTheElem $<<<< (     getTheName
+                                                        &&& getTheType
+                                                        &&& getTheRef
+                                                        &&& getTheSrc
+                                                      )
+                                    ) 
+                                  )
+    where
+    getTheName                  = xshow $
+                                  deep (hasName "b") /> isText
+
+    getTheRef                   = ( single $
+                                    deep (hasName "a" >>> getAttrValue0 "name")
+                                  )
+                                  `withDefault` ""
+
+    getTheType                  = xshow $
+                                  single $
+                                  deep (spanWithClass (== "keyword")) /> isText
+
+    getTheSrc                   = ( single $
+                                    deep (aWithHref ("src/" `isPrefixOf`))
+                                    >>>
+                                    getAttrValue0 "href"
+                                  )
+                                  `withDefault` ""
+
+    mkTheElem n t r s           = eelem "td"
+                                  +=   sattr "class" "decl"
+                                  += ( eelem "a"
+                                       +=   sattr "name" r
+                                       +=   txt (n ++ " :: " ++ t)
+                                     )
+                                  += ( eelem "a"
+                                       += sattr "href" s
+                                       += txt "Source"
+                                     )
+
+-- ------------------------------------------------------------
+
+processCrazySignatures          :: LA XmlTree XmlTree
+processCrazySignatures          = processTopDown
+                                  ( preProcessCrazySignature
+                                    `when`
+                                    getClassPart "rdoc"
+                                  )
+                                  >>>
+                                  processChildren
+                                  ( processDocumentRootElement groupDeclSig declAndDocAndSignatureChildren )
+
+preProcessCrazySignature        :: LA XmlTree XmlTree
+preProcessCrazySignature        = ( selem "tr" 
+                                    [ mkelem "td" [ sattr "class" "signature" ]
+                                                  [ deep (tdWithClass (== "arg"))
+                                                    >>>
+                                                    getChildren
+                                                  ]  
+                                    ] 
+                                    &&&   
+                                    selem "tr" 
+                                    [ mkelem "td" [ sattr "class" "doc" ]
+                                                  [ deep (tdWithClass (== "rdoc"))
+                                                    >>>
+                                                    getChildren
+                                                  ]
+                                    ]
+                                  )
+                                  >>> mergeA (<+>)
+         
+processDocumentRootElement      :: (LA XmlTree XmlTree -> LA XmlTree XmlTree)
+                                ->  LA XmlTree XmlTree
+                                ->  LA XmlTree XmlTree
+processDocumentRootElement theGrouper interestingChildren
+                                = processTopDownUntil
+                                  ( hasName "table"
+                                    `guards`
+                                    ( replaceChildren
+                                      ( processTableRows theGrouper (getChildren >>> interestingChildren) )
+                                    )
+                                  )
+           
+declAndDocAndSignatureChildren :: LA XmlTree XmlTree
+declAndDocAndSignatureChildren = (isDecl <+> isSig <+> isDoc) `guards` this
+
+isDecl                          :: LA XmlTree XmlTree
+isDecl                          = trtdWithClass (== "decl")
+                                  />
+                                  isElemWithAttr "a" "name" (const True)
+
+isDoc                           :: LA XmlTree XmlTree
+isDoc                           = trtdWithClass (== "doc")
+      
+isSig                           :: LA XmlTree XmlTree
+isSig                           = trtdWithClass (== "signature")
+
+getDeclName                     :: LA XmlTree String
+getDeclName                     = (xshow $ single $ getXPathTrees "//tr/td/a/@name/text()") >>^ drop 2
+
+processTableRows                :: (LA XmlTree XmlTree -> LA XmlTree XmlTree)
+                                ->  LA XmlTree XmlTree
+                                ->  LA XmlTree XmlTree
+processTableRows theGrouping ts = theGrouping (remLeadingDocElems ts) {- >>> prune 3 -}
+
+-- regex for a leading class="doc" row
+
+leadingDoc                      :: XmlRegex
+leadingDoc                      = mkStar (mkPrimA isDoc)
+
+-- regex for a class="decl" class="doc" sequence
+
+declSig                         :: XmlRegex
+declSig                         = mkSeq (mkPrimA isDecl) (mkSeq (mkStar (mkPrimA isSig)) leadingDoc)
+
+-- remove a leading class="doc" row this does not form a declaration
+-- split the list of trees and throw away the first part
+
+remLeadingDocElems              :: LA XmlTree XmlTree -> LA XmlTree XmlTree
+remLeadingDocElems ts           = (splitRegexA leadingDoc ts >>^ snd) >>> unlistA
+
+-- group the "tr" trees for a declaration together, build a "tr class="decl"" element and
+-- throw the old "tr" s away
+
+groupDeclSig                    :: LA XmlTree XmlTree -> LA XmlTree XmlTree
+groupDeclSig ts                 = scanRegexA declSig ts 
+                                  >>>
+                                  mkelem  "tr"
+                                  [ sattr "class" "decl"
+                                  , attr "id" (unlistA >>> getDeclName >>> mkText)
+                                  ] 
+                                  [ mkelem "td" [sattr "class" "decl"] 
+                                                [unlistA
+                                                 >>> getXPathTrees "//td[@class='decl' or @class='signature']"
+                                                 >>> getChildren
+                                                ] 
+                                  , mkelem "td" [sattr "class" "doc" ]
+                                                [unlistA
+                                                 >>> getXPathTrees "//td[@class='doc']"
+                                                 >>> getChildren
+                                                ] 
+                                  ]
+
+-- ------------------------------------------------------------
+
+getTitle                        :: LA XmlTree String
+getTitle                        = xshow
+                                  ( getPath "html/head/title"
+                                    >>> deep isText
+                                  )
+
+getPackage                      :: LA XmlTree String
+getPackage                      = getAttrValue transferURI
+                                  >>^   
+                                  hayooGetPackage
+
+addPackageAttr                  :: LA XmlTree XmlTree
+addPackageAttr                  = this += (attr "package" $ getPackage >>> mkText)
+
+addModuleAttr                   :: LA XmlTree XmlTree
+addModuleAttr                   = this += ( attr "module" $ getTitle   >>> mkText)
+
+-- ------------------------------------------------------------
+
+getPath                         :: String -> LA XmlTree XmlTree
+getPath                         = foldl (/>) this . map hasName . split "/"
+
+trtdWithClass                   :: (String -> Bool) -> LA XmlTree XmlTree
+trtdWithClass av                = hasName "tr"
+                                  />
+                                  tdWithClass av
+
+getClassPart                    :: String -> LA XmlTree XmlTree
+getClassPart c                  = trtdWithClass (== "body")
+                                  /> hasName "table"
+                                  /> trtdWithClass (== c)
+
+-- ------------------------------------------------------------
+
+-- mother's little helpers
+
+firstChild                      :: LA XmlTree XmlTree -> LA XmlTree XmlTree
+firstChild sel                  = single (getChildren >>> sel)
+
+
+getById                         :: String -> LA XmlTree XmlTree
+getById id'                     = single (deep (hasAttrValue "id" (== id')))
+
+firstChildWithClass             :: String -> String -> LA XmlTree XmlTree
+firstChildWithClass e c         = firstChild (isElemWithAttr e "class" (== c))
+
+first_p_src                     :: LA XmlTree XmlTree
+first_p_src                     = firstChildWithClass "p" "src"
+ 
+divWithClass                    :: (String -> Bool) -> LA XmlTree XmlTree
+divWithClass                    = isElemWithAttr "div" "class"
+
+spanWithClass                   :: (String -> Bool) -> LA XmlTree XmlTree
+spanWithClass                   = isElemWithAttr "span" "class"
+
+pWithClass                      :: (String -> Bool) -> LA XmlTree XmlTree
+pWithClass                      = isElemWithAttr "p" "class"
+
+aWithClass                      :: (String -> Bool) -> LA XmlTree XmlTree
+aWithClass                      = isElemWithAttr "a" "class"
+
+trWithClass                     :: (String -> Bool) -> LA XmlTree XmlTree
+trWithClass                     = isElemWithAttr "tr" "class"
+
+tdWithClass                     :: (String -> Bool) -> LA XmlTree XmlTree
+tdWithClass                     = isElemWithAttr "td" "class"
+
+aWithHref                       :: (String -> Bool) -> LA XmlTree XmlTree
+aWithHref                       = isElemWithAttr "a" "href"
+
+-- ------------------------------------------------------------
+
+mkDecl0                         :: String -> LA b XmlTree -> LA b XmlTree
+mkDecl0 typ body                = ( eelem "div"
+                                    += sattr "class" "top"
+                                    += sattr "type"  typ
+                                    += body
+                                  )
+
+mkDecl1                         :: String -> LA b XmlTree -> LA b XmlTree -> LA b XmlTree
+mkDecl1 typ src doc             = mkDecl0 typ
+                                  ( ( eelem "p"
+                                      += sattr "class" "src"
+                                      += src
+                                    )
+                                    <+>
+                                    ( eelem "div"
+                                      += sattr "class" "doc"
+                                      += doc
+                                    )
+                                  )
+
+mkFctDecl                       :: LA b XmlTree -> LA b XmlTree -> LA b XmlTree
+mkFctDecl                       = mkDecl1 "function"
+
+mkModuleDecl                    :: LA b XmlTree -> LA b XmlTree -> LA b XmlTree
+mkModuleDecl                    = mkDecl1 "module"
+
+-- ------------------------------------------------------------
+
+{- tests for splitting function signatures for a list of functions
+
+processFunctionSig          :: LA XmlTree XmlTree
+processFunctionSig          = mkSingleFct $< splitMultiFct
+    where
+      mkSingleFct ts        = replaceChildren (constL ts)
+      splitMultiFct         = listA getChildren
+                              >>> spanA (neg $ hasText ((== "::") . stringTrim))
+                              >>> first ( unlistA
+                                          >>> aWithClass (== "def")
+                                          >>> hasAttr "name"
+                                        ) 
+                              >>> arr (uncurry (:))
+
+processFunctionDiv         :: LA XmlTree XmlTree
+processFunctionDiv         = (mkSingleDiv $< splitMultiDiv)
+                              += sattr "type" "function"
+    where
+      mkSingleDiv ts        = replaceChildren (constL ts)
+      splitMultiDiv         = listA getChildren
+                              >>> arr (head &&& tail)
+                              >>> first processFunctionSig
+                              >>> arr (uncurry (:))
+
+x3 = concat $
+     [   "<p class=\"src\">"
+     ,     "<a name=\"v:t_xml\" class=\"def\">t_xml</a>"
+     ,     ", "
+     ,     "<a name=\"v:t_root\" class=\"def\">t_root</a>"
+     ,     " :: "
+     ,     "<a href=\"/packages/archive/base/4.4.1.0/doc/html/Data-String.html#t:String\">String</a>"
+     ,     "<a href=\"src/Text-XML-HXT-DOM-XmlKeywords.html#t_xml\" class=\"link\">Source</a>"
+     ,   "</p>"
+     ]
+
+x4 = concat $
+     [ "<div class=\"top\">"
+     , x3
+     ,   "<div class=\"doc\">"
+     ,     "<p>the predefined namespace uri for xml: &quot;http://www.w3.org/XML/1998/namespace&quot;</p>"
+     ,   "</div>"
+     , "</div>"
+     ]
+
+x2 = "<a><c></c> :: <b></b>::</a>"
+
+show3 = test this x3
+show4 = test this x4
+
+-- split a function def like "f1, f2 :: T"
+-- in two independent declarations "f1 :: T" and "f2 :: T"
+test3 = test processFunctionSig x3
+
+-- split the whole div element containing the function signature
+test4  = test processFunctionDiv x4
+
+test f x = sequence $ map putStrLn $ runLA (xread >>> f >>> xshow indentDoc) $ x
+
+
+-- -}
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/IndexConfig.hs b/src/Hayoo/IndexConfig.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/IndexConfig.hs
@@ -0,0 +1,198 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.IndexConfig
+where
+
+import           Data.Char.Properties.XMLCharProps
+
+import           Hayoo.Haddock
+import           Hayoo.HackagePackage
+import           Hayoo.Signature
+
+import           Holumbus.Crawler
+import           Holumbus.Crawler.IndexerCore
+
+import           Text.Regex.XMLSchema.String      ( matchSubex )
+
+import           Text.XML.HXT.Core
+
+-- ------------------------------------------------------------
+
+hayooIndexContextConfig         :: [IndexContextConfig]
+hayooIndexContextConfig         = [ ixModule
+                                  , ixHierachy
+                                  , ixPackage
+                                  , ixName
+                                  , ixPartial
+                                  , ixSignature
+                                  , ixNormalizedSig
+                                  , ixDescription
+                                  ]
+    where
+    ixDefault                   = IndexContextConfig
+                                  { ixc_name            = "default"
+                                  , ixc_collectText     = getHtmlPlainText
+                                  , ixc_textToWords     = deleteNotAllowedChars >>> words
+                                  , ixc_boringWord      = boringWord
+                                  }
+    ixModule                    = ixDefault
+                                  { ixc_name            = "module"
+                                  , ixc_collectText     = getAttrValue "module"
+                                  , ixc_textToWords     = return
+                                  , ixc_boringWord      = null
+                                  }
+    ixHierachy                  = ixModule
+                                  { ixc_name            = "hierarchy"
+                                  , ixc_textToWords     = tokenize "[^.]+"              -- split module name at .
+                                  }
+    ixPackage                   = ixDefault
+                                  { ixc_name            = "package"
+                                  , ixc_collectText     = getAttrValue "package"
+                                  , ixc_textToWords     = return
+                                  , ixc_boringWord      = \ x -> null x || x == "unknownpackage"
+                                  }
+    ixName                      = ixDefault
+                                  { ixc_name            = "name"
+                                  , ixc_collectText     = getAttrValue "title"          -- is simpler than: fromLA $ getAllText (deep $ trtdWithClass (== "decl")) -- TODO 2.8 version
+                                  , ixc_textToWords     = removePars                    --                  tokenize "[^ ():]+" >>> take 1
+                                  , ixc_boringWord      = null                          -- (`elem` ["type", "class", "data", "module"])
+                                  }
+    ixPartial                   = ixName
+                                  { ixc_name            = "partial"
+                                  , ixc_textToWords     = deCamel >>> tokenize typeIdent
+                                  , ixc_boringWord      = boringWord
+                                  }
+    ixSignature                 = ixDefault
+                                  { ixc_name            = "signature"
+                                  , ixc_collectText     = getAttrValue "signature"
+                                  , ixc_textToWords     = stripSignature >>> return
+                                  , ixc_boringWord      = not . isSignature
+                                  }
+    ixNormalizedSig             = ixSignature
+                                  { ixc_name            = "normalized"
+                                  , ixc_textToWords     = normalizeSignature >>> return
+                                  }
+    ixDescription               = ixDefault
+                                  { ixc_name            = "description"
+                                  , ixc_collectText     = fromLA $ getAllText hayooGetDescr
+                                  , ixc_textToWords     = tokenize descrWord
+                                  }
+
+-- -----------------------------------------------------------------------------    
+
+hayooPkgIndexContextConfig      :: [IndexContextConfig]
+hayooPkgIndexContextConfig      = [ ixCategory
+                                  , ixPkgName
+                                  , ixDepends
+                                  , ixDescription
+                                  , ixSynopsis
+                                  , ixAuthor
+                                  ]
+    where
+    ixDefault                   = IndexContextConfig
+                                  { ixc_name            = "default"
+                                  , ixc_collectText     = getHtmlPlainText
+                                  , ixc_textToWords     = deleteNotAllowedChars >>> words
+                                  , ixc_boringWord      = boringWord
+                                  }
+    ixCategory                  = ixDefault
+                                  { ixc_name            = "category"
+                                  , ixc_collectText     = fromLA getPkgCategory
+                                  , ixc_textToWords     = words
+                                  }
+    ixPkgName                   = ixDefault
+                                  { ixc_name            = "pkgname"
+                                  , ixc_collectText     = fromLA $ getPkgName
+                                  , ixc_textToWords     = splitDash
+                                  }
+    ixDepends                   = ixDefault
+                                  { ixc_name            = "dependencies"
+                                  , ixc_collectText     = fromLA $ getPkgDependencies >>^ unwords
+                                  , ixc_textToWords     = words
+                                  }
+    ixDescription               = ixDefault
+                                  { ixc_name            = "pkgdescr"
+                                  , ixc_collectText     = fromLA $ getPkgDescr
+                                  , ixc_textToWords     = tokenize descrWord
+                                  }
+    ixSynopsis                  = ixDescription
+                                  { ixc_name            = "synopsis"
+                                  , ixc_collectText     = fromLA $ getPkgSynopsis
+                                  }
+    ixAuthor                    = ixDescription
+                                  { ixc_name            = "author"
+                                  , ixc_collectText     = fromLA $
+                                                          listA (getPkgAuthor <+> getPkgMaintainer) >>^ unwords
+                                  }
+
+-- -----------------------------------------------------------------------------    
+
+-- please: "-" as 1. char in set !!!
+-- words start with a letter, end with a letter or digit and may contain -, . and @ and digits
+
+descrWord                       :: String
+descrWord                       = "[A-Za-z][-A-Za-z0-9.@]*[A-Za-z0-9]"
+
+-- -----------------------------------------------------------------------------    
+
+deCamel                         :: String -> String
+deCamel                         = deCamel' False
+    where
+    deCamel' _ []               = []
+    deCamel' _ ('_' : xs)       = ' ' : deCamel' True xs
+    deCamel' cap (x : xs)
+        | isCap x               = ( if cap
+                                    then id
+                                    else (' ' :)
+                                  ) $
+                                  x : deCamel' True      xs
+        | otherwise             = x : deCamel' (isCap x) xs
+        where
+        isCap                   = (`elem` ['A'..'Z'])
+
+-- -----------------------------------------------------------------------------    
+
+boringWord                      :: String -> Bool
+boringWord w                    = null w
+                                  ||
+                                  (null . tail $ w)
+                                  ||
+                                  not (any isXmlLetter w)
+
+isAllowedWordChar               :: Char -> Bool
+isAllowedWordChar c             = isXmlLetter c
+                                  ||
+                                  isXmlDigit c
+                                  ||
+                                  c `elem` "_-"
+
+deleteNotAllowedChars           :: String -> String
+deleteNotAllowedChars           = map notAllowedToSpace
+    where
+    notAllowedToSpace c
+        | isAllowedWordChar c   = c
+        | otherwise             = ' '
+
+splitDash                       :: String -> [String]
+splitDash s                     = ( s : (map (\ x -> if x == '-' then ' ' else x)
+                                         >>>
+                                         words
+                                         >>>
+                                         drop 1
+                                         $
+                                          s
+                                        )
+                                  )
+
+matchPars                       :: String -> [(String, String)]
+matchPars                       = matchSubex "[(]({m}.+)[)]"
+
+removePars                      :: String -> [String]
+removePars xs
+    = case matchPars xs of
+        [("m", xs')]            -> [xs, xs']
+        _                       -> [xs]
+
+-- -----------------------------------------------------------------------------    
diff --git a/src/Hayoo/IndexTypes.hs b/src/Hayoo/IndexTypes.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/IndexTypes.hs
@@ -0,0 +1,163 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.IndexTypes
+    ( module Hayoo.IndexTypes
+    , module Holumbus.Index.CompactIndex
+    , FunctionInfo(..)
+    , PackageInfo(..)
+    , Score
+    )
+where
+
+import           Control.Arrow
+import           Control.DeepSeq
+
+import           Data.Binary
+
+import           Data.List                      ( foldl' )
+import qualified Data.Map                       as M    ( lookup )
+import           Data.Maybe
+
+import           Hayoo.FunctionInfo
+import           Hayoo.PackageInfo
+import           Hayoo.PackageRank
+
+import           Holumbus.Crawler
+import           Holumbus.Crawler.IndexerCore
+import qualified Holumbus.Data.PrefixTree       as PT
+import           Holumbus.Index.Common          ( Document(..)
+                                                , custom
+                                                , elemsDocIdMap
+                                                , emptyDocIdMap
+                                                , emptyPos
+                                                , foldWithKeyDocIdMap
+                                                , insertDocIdMap
+                                                , keysDocIdMap
+                                                , removeById
+                                                , toMap
+                                                , updateDocuments
+                                                )
+import           Holumbus.Index.CompactIndex
+import           Holumbus.Query.Result          ( Score )
+
+-- ------------------------------------------------------------
+
+getPkgNameFct                   :: Document FunctionInfo -> String
+getPkgNameFct                   = package . fromJust . custom
+
+getPkgNamePkg                   :: Document PackageInfo -> String
+getPkgNamePkg                   = p_name . fromJust . custom
+
+-- ------------------------------------------------------------
+
+removePackages'                 :: (Binary di, NFData di) =>
+                                   (Document di -> String) -> String ->
+                                   [String] -> Bool -> IO (HolumbusState di)
+removePackages' pkgName ixName pkgList defragment
+                                = do
+                                  ix <- decodeFile ixName
+                                  let ix1  = removePack' pkgName pkgList ix
+                                  let ix2  = if defragment
+                                             then defragmentHolumbusState ix1
+                                             else ix1
+                                  rnf ix2 `seq`
+                                      return ix2
+
+-- ------------------------------------------------------------
+
+removePack'                     :: (Binary di) =>
+                                   (Document di -> String) -> [String] ->
+                                   HolumbusState di -> HolumbusState di
+removePack' pkgName ps IndexerState
+              { ixs_index     = ix
+              , ixs_documents = ds
+              }                 = IndexerState
+                                  { ixs_index     = ix'
+                                  , ixs_documents = ds'
+                                  }
+    where
+                                                        -- collect all DocIds used in the given packages
+    docIds                      = foldWithKeyDocIdMap checkDoc emptyDocIdMap . toMap $ ds
+    checkDoc did doc xs
+        | docPartOfPack         = insertDocIdMap did emptyPos xs
+        | otherwise             =                             xs
+        where
+        docPartOfPack           = (`elem` ps) . pkgName $ doc
+
+                                                        -- remove all DocIds from index
+    ix'                         = removeDocIdsInverted docIds ix
+
+                                                        -- restrict document table
+    ds'                         = foldl' removeById ds $ keysDocIdMap docIds
+
+-- ------------------------------------------------------------
+
+-- | package ranking is implemented by the following algorithm
+--
+-- .1 the rank of a package not used by another package is 1.0
+--
+-- .2 the rank of a package used by other packages is 1.0 + 0.5 * sum of the ranks of the
+--    directly dependent packages. Example: a depends on b, b depends on c, d depends on c:
+--    rank a = 1.0, rank b = 1.5, rank c = 2.25, rank d = 1.0
+--
+-- .3 this leads to a ranking where rank base > 1000.0 and rank bytestring > 300. To
+--    reduce the weight differences, the log to base 2 is taken instead of the direct value
+
+packageRanking                  :: HayooPkgIndexerState -> HayooPkgIndexerState
+packageRanking ixs@(IndexerState { ixs_documents = ds })
+                                = ixs { ixs_documents = updateDocuments insertRank ds }
+    where
+    deflate                     = 0.5
+    scale                       = (/10.0) . fromInteger . round . (*10) . (+1.0) . logBase 2
+    rank                        = ranking deflate
+                                  . dagFromList
+                                  . map (\ p -> (getPackageName p, getPackageDependencies p))
+                                  . map fromJust
+                                  . filter isJust               -- all illegal package refs are filtered out (there are illegal refs)
+                                  . map custom
+                                  . elemsDocIdMap
+                                  . toMap $ ds
+
+    insertRank d                = d { custom = fmap insertRank' (custom d) }
+        where
+        insertRank' ci          = setPackageRank (scale . fromMaybe (1.0) . M.lookup (getPackageName ci) $ rank) ci
+
+{-
+traceNothing d
+    | isJust . custom $ d       = d
+    | otherwise                 = traceShow d $ d
+-- -}
+
+-- ------------------------------------------------------------
+
+type RankTable                  = PT.PrefixTree Score
+
+lookupRankTable                 :: String -> RankTable -> Score
+lookupRankTable p               = fromMaybe 1.0 . PT.lookup p
+
+buildRankTable                  :: SmallDocuments PackageInfo -> RankTable
+buildRankTable                  = toMap
+                                  >>> elemsDocIdMap
+                                  >>> map ( custom
+                                            >>> fromJust
+                                            >>> (p_name &&& p_rank)
+                                          )
+                                  >>> PT.fromList 
+
+-- ------------------------------------------------------------
+
+type HayooIndexerState                  = HolumbusState   FunctionInfo
+type HayooIndexerConfig                 = HolumbusConfig  FunctionInfo
+
+type HayooIndexerCrawlerState           = CrawlerState HayooIndexerState
+
+-- ------------------------------------------------------------
+
+type HayooPkgIndexerState               = HolumbusState   PackageInfo
+type HayooPkgIndexerConfig              = HolumbusConfig  PackageInfo
+
+type HayooPkgIndexerCrawlerState        = CrawlerState HayooPkgIndexerState
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/PackageArchive.hs b/src/Hayoo/PackageArchive.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/PackageArchive.hs
@@ -0,0 +1,74 @@
+module Hayoo.PackageArchive
+where
+
+
+import qualified Codec.Archive.Tar              as Tar
+import qualified Codec.Archive.Tar.Entry        as Tar
+
+import qualified Codec.Compression.GZip         as GZip ( decompress )
+
+import           Control.Arrow
+
+import           Data.ByteString.Lazy           ( ByteString )
+import qualified Data.ByteString.Lazy           as BS
+import           Data.List
+
+import           System.FilePath
+import           System.Process                 ( system )
+import           System.Time
+
+-- ------------------------------------------------------------
+
+getNewPackages          :: Bool -> Int -> IO [String]
+getNewPackages updateArchive since
+                        = do
+                          if updateArchive
+                             -- this is a hack, should be done more elegant without the use of system and wget
+                             then system ("wget http://hackage.haskell.org/packages/" ++ archiveFile ++ " -O cache/" ++ archiveFile)
+                                  >> return ()
+                             else return ()
+                          t <- secondsAgo
+                          a <- BS.readFile $ "cache/" ++ archiveFile
+                          return $ latestPackages t a
+    where
+    archiveFile         = "00-index.tar.gz"
+    secondsAgo          :: IO ClockTime
+    secondsAgo          = do
+                          (TOD s _f) <- getClockTime
+                          return $ TOD (s - toInteger since) 0
+
+-- ------------------------------------------------------------
+
+latestPackages          :: ClockTime -> ByteString -> [String]
+latestPackages since    =  nub . sort . map fst . filterPackages since . selectPackages
+
+filterPackages          :: ClockTime -> [(String, (String, ClockTime))] -> [(String, (String, ClockTime))]
+filterPackages since    = filter ((since <=) . snd . snd)
+
+selectPackages          :: ByteString -> [(String, (String, ClockTime))]
+selectPackages          = GZip.decompress
+                          >>> Tar.read
+                          >>> Tar.foldEntries (\ e -> (entryInfo e ++)) [] (const [])
+
+entryInfo               :: Tar.Entry -> [(String, (String, ClockTime))]
+entryInfo e
+    | isFile e
+      &&
+      isCabal path      = [(package, (version, time))]
+    | otherwise         = []
+    where
+    path                = Tar.entryPath e
+    (version : package : _)
+                        = splitDirectories >>> reverse >>> tail $ path
+    time                = Tar.entryTime >>> epochTimeToClockTime $ e
+    isCabal             = takeExtension >>> (== ".cabal")
+    isFile e'           = case Tar.entryContent e' of
+                          Tar.NormalFile _ _    -> True
+                          _                     -> False
+
+epochTimeToClockTime :: Tar.EpochTime -> ClockTime
+epochTimeToClockTime e = TOD s (truncate (1000000000 * f))
+    where (s,f) = properFraction (toRational e)
+
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/PackageInfo.hs b/src/Hayoo/PackageInfo.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/PackageInfo.hs
@@ -0,0 +1,104 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.PackageInfo
+where
+
+import           Control.DeepSeq
+
+import           Data.Binary                    ( Binary(..) )
+import qualified Data.Binary                    as B
+
+import           Holumbus.Query.Result          ( Score )
+
+import           Text.XML.HXT.Core
+
+-- ------------------------------------------------------------
+
+-- | Additional information about a function.
+--
+-- The strings in this record are not compressed into bytestrings.
+-- The document table contains bzip compressed bytestings of serialized
+-- records, which are unpacked when accessing the document descriptions.
+-- So there is non need for unsing bytestrings and strict fields
+
+data PackageInfo
+    = PackageInfo 
+      { p_name              :: String               -- ^ The name of the package
+      , p_version           :: String               -- ^ The latest package version
+      , p_dependencies      :: String               -- ^ The list of required packages
+      , p_author            :: String               -- ^ The author
+      , p_maintainer        :: String               -- ^ The maintainer
+      , p_category          :: String               -- ^ The package category
+      , p_homepage          :: String               -- ^ The home page
+      , p_synopsis          :: String               -- ^ The synopsis
+      , p_description       :: String               -- ^ The description of the package
+      , p_rank              :: Score                -- ^ The ranking
+      } 
+    deriving (Show, Eq)
+
+mkPackageInfo                   :: String -> String -> [String] -> String -> String -> String -> String -> String -> String -> PackageInfo
+mkPackageInfo n v d a m c h y s = PackageInfo n v (unwords d) a m c h y s 1.0
+
+setPackageRank                  :: Score -> PackageInfo -> PackageInfo
+setPackageRank r p              = r `seq` p { p_rank = r }
+
+getPackageName                  :: PackageInfo -> String
+getPackageName                  =  p_name
+
+getPackageDependencies          :: PackageInfo -> [String]
+getPackageDependencies          = words . p_dependencies
+
+instance XmlPickler PackageInfo where
+    xpickle                     = xpWrap (fromTuple, toTuple) xpPackage
+        where
+        fromTuple ((n, v, d), a, m, c, h, (y, s, r))
+                                = PackageInfo n v d a m c h y s r
+        toTuple (PackageInfo n v d a m c h y s r)
+                                = ((n, v, d)
+                                  , a, m, c, h
+                                  ,(y, s, r)
+                                  )
+        xpPackage               = xp6Tuple
+                                  (xpTriple xpName xpVersion xpDependencies)
+                                  xpAuthor xpMaintainer xpCategory xpHomepage
+                                  (xpTriple xpSynopsis xpDescr xpRank)
+            where                               
+            xpName              = xpAttr "name"         xpText0
+            xpVersion           = xpAttr "version"      xpText0
+            xpDependencies      = xpAttr "dependencies" xpText0
+            xpAuthor            = xpAttr "author"       xpText0
+            xpMaintainer        = xpAttr "maintainer"   xpText0
+            xpCategory          = xpAttr "category"     xpText0
+            xpHomepage          = xpAttr "homepage"     xpText0
+            xpSynopsis          = xpAttr "synopsis"     xpText0
+            xpDescr             = xpText
+            xpRank              = xpAttr "rank" $
+                                  xpWrap (read, show)   xpText0
+
+instance NFData PackageInfo where
+  rnf (PackageInfo n v d a m c h y s r)
+                                = rnf n `seq` rnf v `seq` rnf d `seq` rnf a `seq`
+                                  rnf m `seq` rnf c `seq` rnf h `seq` rnf y `seq`
+                                  rnf s `seq` r          `seq` ()
+
+instance B.Binary PackageInfo where
+    put (PackageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9 x10)
+                                = put x1 >> put x2 >> put x3 >> put x4 >> put x5 >>
+                                  put x6 >> put x7 >> put x8 >> put x9 >> put x10
+    get                         = do
+                                  x1 <- get
+                                  x2 <- get
+                                  x3 <- get
+                                  x4 <- get
+                                  x5 <- get
+                                  x6 <- get
+                                  x7 <- get
+                                  x8 <- get
+                                  x9 <- get
+                                  x10 <- get
+                                  let r = PackageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9 x10
+                                  rnf r `seq` return r
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/PackageRank.hs b/src/Hayoo/PackageRank.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/PackageRank.hs
@@ -0,0 +1,126 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.PackageRank
+where
+
+import           Control.Arrow
+
+import           Data.Map       ( Map )
+import qualified Data.Map       as M
+import           Data.Maybe
+import           Data.Set       ( Set )
+import qualified Data.Set       as S
+{-
+import           Debug.Trace
+-- -}
+-- ------------------------------------------------------------
+
+type DAG a                      = Map a (Set a)
+
+type Ranking a                  = Map a Double
+
+-- ------------------------------------------------------------
+
+-- | construct a directed graph form a list of nodes and successors.
+--
+--  This version does not check cycles, so it only works savely,
+--  when this is checked before.
+
+unsaveDagFromList               :: (Ord a, Show a) => [(a, [a])] -> DAG a
+unsaveDagFromList l             = -- traceShow l $
+                                  map (second S.fromList) >>> M.fromList $ l
+
+-- | Construct a directed graph (DAG) form a list of nodes and successors.
+--
+-- The function checks for edges, that would introduce cycles, and deletes these
+-- edges. So if there are cycles in the input list, the result depends on the
+-- sequence of the pairs in the input list
+
+dagFromList                     :: (Ord a, Show a) => [(a, [a])] -> DAG a
+dagFromList l                   = -- traceShow l $
+                                  map (second S.fromList)
+                                  >>>
+                                  foldl (flip insEdges) M.empty $ l
+
+insEdges                        :: (Ord a, Show a) => (a, Set a) -> DAG a -> DAG a
+insEdges (x, ys) g              = S.fold insEdge' g $ ys
+    where
+    insEdge' y' g'              = insertEdge x y' g'
+
+-- ------------------------------------------------------------
+
+-- | insert an edge from x to y into DAG g.
+--
+-- Check for possible cycles. Edges leading to cycles are discarded
+
+insertEdge                      :: (Ord a, Show a) => a -> a -> DAG a -> DAG a
+insertEdge x y g
+    | x == y                    = -- traceShow ("cycle:", [x,y]) $
+                                  g
+    | existPath                 = -- traceShow ("cycle:", x:(head path)) $
+                                  g
+    | otherwise                 = M.insertWith S.union x (S.singleton y) g
+    where
+    path                        = take 1 $ allPaths g y x
+    existPath                   = not . null $ path
+
+-- ------------------------------------------------------------
+
+-- | Compute all paths from one node to another.
+--
+-- this is used by insertEdge, when checking for cycles
+
+allPaths                        :: (Ord a) => DAG a -> a -> a -> [[a]]
+allPaths g                      = allPaths'
+    where
+    allPaths' x y
+        | y `S.member` succs    = [[x,y]]
+        | otherwise             = map (x:) . concatMap (flip allPaths' y) . S.toList $ succs
+        where
+        succs                   = fromMaybe S.empty . M.lookup x $ g
+
+-- ------------------------------------------------------------
+
+-- | Inverse to dagFromList
+
+dagToList                       :: DAG a -> [(a, [a])]
+dagToList                       = M.toList >>> map (second S.toList)
+
+-- ------------------------------------------------------------
+
+-- | Switch the direction in the DAG
+
+dagInvert                       :: (Ord a) => DAG a -> DAG a
+dagInvert                       = M.foldrWithKey invVs M.empty
+    where
+    invVs k ks acc              = S.fold invV acc1 $ ks
+        where
+        acc1                    = M.insertWith S.union k  S.empty         $ acc         -- don't forget the roots
+        invV k' acc'            = M.insertWith S.union k' (S.singleton k) $ acc'
+
+-- ------------------------------------------------------------
+
+ranking                         :: (Ord a, Show a) => Double -> DAG a -> Ranking a
+ranking w g                     = -- traceShow r
+                                  r
+    where
+    g'                          = dagInvert g
+    r                           = foldl insertRank M.empty $ M.keys g
+        where
+        insertRank r' k         = M.insert k (w * (S.fold accRank (1/w) usedBy)) r'
+            where
+            usedBy              = fromMaybe S.empty . M.lookup k $ g'
+            accRank k' acc'     = ( fromJust . M.lookup k' $ r ) + acc'
+
+-- ------------------------------------------------------------
+{- minimal test case
+
+d1 :: DAG Int
+d1 = dagFromList [(1,[2,3])
+                 ,(2,[3,4])
+                 ,(3,[]),(4,[])
+                 ]
+-- -}
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/Search/Application.hs b/src/Hayoo/Search/Application.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/Application.hs
@@ -0,0 +1,172 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.SearchApplication
+  Copyright  : Copyright (C) 2010 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  The search web-service for the Hayoo Haskell API search engine.
+
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Hayoo.Search.Application
+    ( hayooApplication
+    , hayooInit
+    , Core (..)
+    )
+where
+
+import Control.Concurrent               -- For the global MVar
+
+import Data.ByteString.Lazy.Char8       ( fromChunks )
+
+import qualified Data.List              as L
+import qualified Data.Text.Encoding     as T
+
+import Hayoo.IndexTypes
+import Hayoo.Search.EvalSearch
+import Hayoo.Search.Pages.Template
+import Hayoo.Search.Pages.Static
+
+import Holumbus.Index.Common
+
+import Hack
+import Hack.Contrib.Middleware.File
+import Hack.Contrib.Middleware.URLMap
+import Hack.Contrib.Request             ( params, path, host )
+import Hack.Contrib.Utils               ( empty_app )
+
+import System.IO                        ( stdout )
+import System.Time
+
+import System.Log.Logger
+import System.Log.Handler.Simple
+
+import qualified
+       Text.XHtmlCombinators            as X
+
+-- ------------------------------------------------------------
+
+-- | Init Hayoo!
+hayooInit :: FilePath -> IO Application
+hayooInit ixBase = do
+    fdl  <- fileHandler "hayoo.log" INFO
+    sdl  <- streamHandler stdout INFO
+
+    updateGlobalLogger rootLoggerName (setHandlers [fdl, sdl])
+    updateGlobalLogger rootLoggerName (setLevel INFO)
+
+    idx  <- loadIndex     hayooIndex
+    infoM "Hayoo.Main" ("Hayoo index   loaded from file " ++ show hayooIndex)
+
+    doc  <- loadDocuments hayooDocs
+    infoM "Hayoo.Main" ("Hayoo docs    loaded from file " ++ show hayooDocs )
+    infoM "Hayoo.Main" ("Hayoo docs contains " ++ show (sizeDocs doc) ++ " functions and types")
+
+    pidx <- loadIndex     hackageIndex
+    infoM "Hayoo.Main" ("Hackage index loaded from file " ++ show hackageIndex)
+
+    pdoc <- loadPkgDocs   hackageDocs
+    infoM "Hayoo.Main" ("Hackage docs  loaded from file " ++ show hackageDocs)
+    infoM "Hayoo.Main" ("Hackage docs contains " ++ show (sizeDocs pdoc) ++ " packages")
+
+    prnk <- return $ buildRankTable pdoc
+    infoM "Hayoo.Main" ("Hackage package rank table computed")
+
+    tpl  <- return $ makeTemplate (sizeDocs pdoc) (sizeDocs doc)
+
+    midct <- newMVar $
+             Core
+             { index      = idx
+             , documents  = doc
+             , pkgIndex   = pidx
+             , pkgDocs    = pdoc
+             , template   = tpl
+             , packRank   = prnk
+             }
+
+    return $
+           url_map [ ("/hayoo.html", hayooApplication midct)
+                   , ("/hayoo.json", hayooApplication midct)
+                   , ("/help.html", serveStatic $ tpl help)
+                   , ("/about.html", serveStatic $ tpl about)
+                   , ("/api.html", serveStatic $ tpl api)
+                   ] (file Nothing empty_app)
+  where
+  hayooIndex      = ixBase ++ "/ix.bin.idx"
+  hayooDocs       = ixBase ++ "/ix.bin.doc"
+  hackageIndex    = ixBase ++ "/pkg.bin.idx"
+  hackageDocs     = ixBase ++ "/pkg.bin.doc"
+  serveStatic c _ = return $ Response 
+                    { status = 200
+                    , headers = [ ("Content-Type", "text/html") ]
+                    , body = fromChunks [T.encodeUtf8 $ X.render c]
+                    }
+
+-- | Generate the actual response
+hayooApplication :: MVar Core -> Env -> IO Response
+hayooApplication midct env      = let p = params env in do
+          request <- return $               getValDef p "query"  ""
+          start   <- return $ readDef 0    (getValDef p "start"  "")
+          static  <- return $ readDef True (getValDef p "static" "")
+          json    <- return $ isJson (path env)
+
+          -- Output some information about the request.
+          logRequest env
+
+          -- Because index access is read only, the MVar's are just read 
+          -- to make them avaliable again.
+          idct    <- readMVar midct
+
+          -- Put all information relevant for rendering into a container
+          state   <- return $ (request, start, static, (template idct))
+
+          -- If the query is empty, just render an empty page
+          resp    <- return $
+                     if L.null request
+                     then renderEmpty json idct
+                     else renderResult state json idct
+
+          -- Determine the mime type of the response
+          mime    <- return $ 
+                     if json 
+                     then "application/json" 
+                     else "text/html"
+
+          -- Return the actual response
+          return $ Response { status = 200, headers = [ ("Content-Type", mime) ], body = resp }
+
+
+-- | Log a request to stdout.
+logRequest :: Env -> IO ()
+logRequest env = do
+  -- Extract remote host and the search string from the incoming transaction.
+  remHost    <- return $ host env
+  rawRequest <- return $ getValDef (params env) "query" ""
+  start      <- return $ getValDef (params env) "start" "0"
+  userAgent  <- return $ getValDef (http env) "User-Agent" "No user agent"
+
+  -- Decode URI encoded entities in the search string.
+  decodedRequest <- return $ decode rawRequest
+
+  -- Get the current date and time.
+  unixTime <- getClockTime
+  currTime <- return $ calendarTimeToString $ toUTCTime unixTime
+
+  -- Output all the collected information from above to stdout.
+  infoM "Hayoo.Request" (currTime ++ "\t" ++
+                         remHost ++ "\t "++
+                         userAgent ++ "\t" ++
+                         rawRequest ++ "\t" ++
+                         decodedRequest ++ "\t" ++
+                         start
+                        )
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/Search/Common.hs b/src/Hayoo/Search/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/Common.hs
@@ -0,0 +1,38 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.Search.Common
+  Copyright  : Copyright (C) 2008 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  Helper functions and types used by the Hayoo crawler and the
+  Hayoo web search.
+
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Hayoo.Search.Common where
+
+import Holumbus.Query.Result    ( Result )
+
+import Hayoo.IndexTypes         ( FunctionInfo, PackageInfo )
+
+-- ----------------------------------------------------------------------------
+
+-- Status information of query processing (status message, result, top modules, top packages).
+
+type StatusResult
+    = ( String
+      , Result FunctionInfo
+      , Result PackageInfo
+      , [(String, Int)]
+      , [(String, Int)]
+      )
+
+-- ----------------------------------------------------------------------------
diff --git a/src/Hayoo/Search/EvalSearch.hs b/src/Hayoo/Search/EvalSearch.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/EvalSearch.hs
@@ -0,0 +1,334 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.SearchApplication
+  Copyright  : Copyright (C) 2010 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  The search web-service for the Hayoo Haskell API search engine.
+
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Hayoo.Search.EvalSearch
+    ( Core(..)
+
+    , parseQuery
+    , genResult
+    , emptyRes
+
+    , renderEmpty
+    , renderResult
+
+    , isJson
+    , renderJson
+    , renderEmptyJson
+
+    , examples
+    , filterStatusResult
+
+    , decode
+
+    , readDef
+    , getValDef
+
+    , loadIndex
+    , loadDocuments
+    , loadPkgDocs
+    )
+where
+
+import Data.ByteString.Lazy.Char8       ( ByteString
+                                        , pack
+                                        , fromChunks
+                                        )
+import Data.Function
+import Data.Maybe
+
+import qualified Data.List              as L
+import qualified Data.Map               as M
+import qualified Data.Text.Encoding     as T
+
+import Data.String.Unicode
+
+import Holumbus.Index.Common
+
+import Holumbus.Query.Language.Grammar
+import Holumbus.Query.Processor
+import Holumbus.Query.Result
+import Holumbus.Query.Ranking
+import Holumbus.Query.Fuzzy
+
+import Holumbus.Utility
+
+import Hayoo.IndexTypes
+import Hayoo.Signature
+
+import Hayoo.Search.Common
+import Hayoo.Search.JSON
+import Hayoo.Search.HTML
+import Hayoo.Search.Parser
+
+import Hayoo.Search.Pages.Template
+import Hayoo.Search.Pages.Static
+
+import Network.URI                      ( unEscapeString )
+
+import System.FilePath                  ( takeExtension )
+
+import qualified
+       Text.XHtmlCombinators            as X
+
+import Text.XML.HXT.Core
+
+-- ------------------------------------------------------------
+
+data Core = Core
+          { index     :: !  CompactInverted
+          , documents :: ! (SmallDocuments FunctionInfo)
+          , pkgIndex  :: !  CompactInverted
+          , pkgDocs   :: ! (SmallDocuments PackageInfo)
+          , template  :: !  Template
+          , packRank  :: !  RankTable
+          }
+
+-- ------------------------------------------------------------
+
+-- | Weights for context weighted ranking.
+contextWeights          :: [(Context, Score)]
+contextWeights          = [ ("name",        0.9)
+                          , ("partial",     0.8)
+                          , ("module",      0.7)
+                          , ("hierarchy",   0.6)
+                          , ("package",     0.5)
+                          , ("signature",   0.4)
+                          , ("description", 0.2)
+                          , ("normalized",  0.1)
+                          ]
+
+-- ------------------------------------------------------------
+
+-- | Just an alias with explicit type.
+loadIndex       :: FilePath -> IO CompactInverted
+loadIndex       = loadFromFile
+
+-- | Just an alias with explicit type.
+loadDocuments   :: FilePath -> IO (SmallDocuments FunctionInfo)
+loadDocuments   = loadFromFile
+
+-- | Just an alias with explicit type.
+loadPkgDocs     :: FilePath -> IO (SmallDocuments PackageInfo)
+loadPkgDocs     = loadFromFile
+
+-- ------------------------------------------------------------
+
+-- Read or use default value
+readDef                         :: Read a => a -> String -> a
+readDef d                       = fromMaybe d . readM
+
+getValDef                       :: [(String,String)] -> String -> String -> String
+getValDef l k d                 = fromMaybe d (lookup k l)
+
+-- | Enable handling of parse errors from 'read'.
+readM                           :: (Read a, Monad m) => String -> m a
+readM s                         = case reads s of
+                                  [(x, "")] -> return x
+                                  _         -> fail "No parse"
+
+-- ------------------------------------------------------------
+
+-- | Decode any URI encoded entities and transform to unicode.
+decode                          :: String -> String
+decode                          = fst . utf8ToUnicode . unEscapeString   -- with urlDecode the + disapears
+
+-- | Perform some postprocessing on the status and the result.
+filterStatusResult              :: String -> StatusResult -> StatusResult
+filterStatusResult q (s, r@(Result dh wh), h, m, p)
+                                = (s, filteredResult, h, m, p)
+  where
+  filteredResult
+      | isSignature q           = r
+      | otherwise               = Result dh (M.filterWithKey (\x _y -> not . isSignature $ x) wh)
+
+-- | Just render an empty page/JSON answer
+
+renderEmpty                     :: Bool -> Core -> ByteString
+renderEmpty j idct
+    | j                         = writeJson
+    | otherwise                 = writeHtml
+    where
+    writeJson                   = pack $ renderEmptyJson
+    writeHtml                   = fromChunks [T.encodeUtf8 $ X.render $ (template idct) examples]
+
+-- | Parse the query and generate a result or an error depending on the parse result.
+
+renderResult :: (String, Int, Bool, Template) -> Bool -> Core  -> ByteString
+renderResult (r, s, i, t) j idct
+                        = decode
+                          >>>
+                          parseQuery
+                          >>>
+                          either emptyRes (genResult idct)
+                          >>>
+                          ( if j
+                            then pack . renderJson
+                            else writeHtml (RenderState r s i)
+                          )
+                          $ r
+      where
+      writeHtml rs      = filterStatusResult r
+                          >>>
+                          arr (applyTemplate rs)
+      applyTemplate rs sr
+                        = fromChunks [T.encodeUtf8 markup]
+          where
+          markup        = let rr = result rs sr in 
+                          if rsStatic rs
+                          then X.render $ t rr
+                          else X.render $ rr
+
+-- Check requested path for JSON
+isJson                  :: FilePath -> Bool
+isJson f                = takeExtension f == ".json"
+
+-- ------------------------------------------------------------
+
+hayooPkgRanking         :: RankTable -> DocId -> DocInfo PackageInfo -> DocContextHits -> Score
+hayooPkgRanking rt _ di _
+                        = maybe 1.0 (flip lookupRankTable rt . p_name) (custom $ document di)
+
+-- | Customized Hayoo! ranking function for functions. Preferres exact matches and matches in Prelude and base.
+hayooFctRanking                 :: RankTable -> [(Context, Score)] -> [String] -> DocId -> DocInfo FunctionInfo -> DocContextHits -> Score
+hayooFctRanking rt ws ts _ di dch
+                        = baseScore
+                          * factModule
+                          * factPackage
+                          * factPrelude
+                          * factExactMatch
+  where
+  fctInfo               = custom $ document di
+
+  baseScore             = M.foldrWithKey calcWeightedScore 0.0 dch
+
+  factExactMatch        = L.foldl' (\r t -> t == (title $ document di) || r) False
+                          >>> fromEnum
+                          >>> (+ 1)
+                          >>> fromIntegral
+                          >>> (* 4.0)
+                          $ ts
+
+  factPrelude           = fmap ( moduleName
+                                 >>> (== "Prelude")
+                                 >>> fromEnum
+                                 >>> (+ 1)
+                                 >>> fromIntegral
+                                 >>> (* 2.0)
+                               )
+                          >>> fromMaybe 1.0
+                          $ fctInfo
+
+  factPackage           = fmap ( package
+                                 >>> flip lookupRankTable rt
+                               )
+                          >>> fromMaybe 1.0
+                          $ fctInfo
+
+  factModule            = fmap ( moduleName
+                                 >>> split "."
+                                 >>> length
+                                 >>> fromIntegral
+                                 >>> (1.0 /)
+                               )
+                          >>> fromMaybe 1.0
+                          $ fctInfo
+
+  calcWeightedScore     :: Context -> DocWordHits -> Score -> Score
+  calcWeightedScore c h r
+                        = maybe r (\w -> r + ((w / mw) * count)) (lookupWeight ws)
+    where
+    count               = fromIntegral $ M.fold ((+) . sizePos) 0 h
+    mw                  = snd $ L.maximumBy (compare `on` snd) ws
+    lookupWeight []     = Nothing
+    lookupWeight (x:xs) = if fst x == c then
+                            if snd x /= 0.0
+                            then Just (snd x)
+                            else Nothing
+                          else lookupWeight xs
+
+emptyRes                :: String -> StatusResult
+emptyRes msg            = ( tail . dropWhile ((/=) ':') $ msg
+                          , emptyResult
+                          , emptyResult
+                          , []
+                          , []
+                          )
+
+genResult ::  Core -> Query -> StatusResult
+genResult idc q
+      = let (fctRes, pkgRes) = curry makeQuery q idc in
+        let (fctCfg, pkgCfg) = ( RankConfig (hayooFctRanking (packRank idc) contextWeights (extractTerms q)) wordRankByCount
+                               , RankConfig (hayooPkgRanking (packRank idc))                                 wordRankByCount
+                               ) in
+        let (fctRnk, pkgRnk) = ( rank fctCfg fctRes
+                               , rank pkgCfg pkgRes
+                               ) in
+        ( msgSuccess fctRnk pkgRnk
+        , fctRnk
+        , pkgRnk
+        , genModules fctRnk
+        , genPackages fctRnk
+        ) -- Include a success message in the status
+
+-- | Generate a success status response from a query result.
+msgSuccess              :: Result FunctionInfo -> Result PackageInfo -> String
+msgSuccess fr pr        = if sd + sp == 0
+                          then "Nothing found yet."
+                          else "Found " ++ (show sd) ++ " " ++ ds ++ ", " ++ (show sp) ++ " " ++ ps ++ " and " ++ (show sw) ++ " " ++ cs ++ "."
+    where
+    sd                  = sizeDocHits fr
+    sp                  = sizeDocHits pr
+    sw                  = sizeWordHits fr + sizeWordHits pr
+    ds                  = if sd == 1 then "function" else "functions"
+    ps                  = if sp == 1 then "package" else "packages"
+    cs                  = if sw == 1 then "completion" else "completions"
+
+-- | This is where the magic happens! This helper function really calls the
+-- processing function which executes the query.
+makeQuery               :: (Query, Core) -> (Result FunctionInfo, Result PackageInfo)
+makeQuery (q, c)        = (processQuery cfg (index c) (documents c) q, processQuery cfg (pkgIndex c) (pkgDocs c) q)
+    where
+    cfg                 = ProcessConfig
+                          { fuzzyConfig   = FuzzyConfig False True 1.0 []
+                          , optimizeQuery = True
+                          , wordLimit     = 50
+                          , docLimit      = 500
+                          }
+
+-- | Generate a list of modules from a result
+genModules              :: Result FunctionInfo -> [(String, Int)]
+genModules r            = reverse $
+                          L.sortBy (compare `on` snd) $
+                          M.toList $
+                          foldDocIdMap collectModules M.empty (docHits r)
+  where
+  collectModules ((DocInfo d _), _)  modules
+                        = maybe modules (\fi -> M.insertWith (+) (takeWhile (/= '.') . moduleName $ fi) 1 modules) $
+                          custom d
+
+genPackages             :: Result FunctionInfo -> [(String, Int)]
+genPackages r           = reverse $
+                          L.sortBy (compare `on` snd) $
+                          M.toList $
+                          foldDocIdMap collectPackages M.empty (docHits r)
+  where
+  collectPackages ((DocInfo d _), _) packages
+                        = maybe packages (\fi -> M.insertWith (+) (package fi) 1 packages) $
+                          custom d
+
+-- ----------------------------------------------------------------------------
diff --git a/src/Hayoo/Search/HTML.hs b/src/Hayoo/Search/HTML.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/HTML.hs
@@ -0,0 +1,243 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.Search.HTML
+  Copyright  : Copyright (C) 2008 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  The conversion functions for generating the HTML code of an Hayoo!
+  search result.
+ 
+-}
+
+-- ----------------------------------------------------------------------------
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Hayoo.Search.HTML 
+    ( RenderState (..)
+    , result
+    )
+where
+
+import           Data.Char      ( toLower
+                                , isSpace
+                                )
+import           Data.Function
+import qualified Data.List      as L
+import qualified Data.Map       as M
+import           Data.Maybe
+import           Data.Text      (Text, pack)
+
+import           Text.XHtmlCombinators
+import qualified Text.XHtmlCombinators.Attributes as A
+
+import           Holumbus.Utility hiding (escape)
+import           Holumbus.Index.Common
+
+import           Holumbus.Query.Result
+
+import           Hayoo.IndexTypes
+import           Hayoo.Search.Common
+
+data RenderState = RenderState
+                 { rsQuery    :: String
+                 , rsStart    :: Int
+                 , rsStatic   :: Bool
+                 }
+
+pageLimit :: Int
+pageLimit = 10
+
+maxWordHits :: Int
+maxWordHits = 75
+
+staticRoot :: String
+staticRoot = "hayoo.html"
+
+text' :: (Functor t, Monad t, CData c) => String -> XHtmlT t c
+text' = text . pack
+
+onclick :: Text -> Attr
+onclick = A.attr "onclick"
+
+-- | Render the whole Hayoo! search result.
+result :: RenderState -> StatusResult -> XHtml FlowContent
+result rs (sm, rf, rp, tm, tp) = div' [A.id_ "result"] $ do
+  div' [A.id_ "status"] $ text' sm  
+  packageResults rs rp
+  topLists rs tm tp
+  functionResults rs rf
+  div' [A.class_ "clear"] $ empty
+  pager rs $ max (sizeDocHits rf) (sizeDocHits rp)
+
+-- | The aggregation lists.
+topLists :: RenderState -> [(String, Int)] -> [(String, Int)] -> XHtml FlowContent
+topLists _ [] [] = empty
+topLists rs tm tp = div' [A.id_ "aggregation"] $ do
+  moduleList rs tm
+  packageList rs tp
+
+-- | The aggregated module list.
+moduleList :: RenderState -> [(String, Int)] -> XHtml FlowContent
+moduleList _ [] = empty
+moduleList rs tm = div' [A.id_ "modules"] $ do
+  div' [A.class_ "headline"] $ text "Top 15 Modules"
+  mapM_ topModule (take 15 tm)
+  where
+  topModule (m, c) = div' [A.class_ "rootModule"] $ do
+    a' [A.class_ "rootModuleName", A.href staticLink, onclick dynamicLink] $ text' m
+    text " "
+    span' [A.class_ "rootModuleCount"] $ text' $ show c
+      where
+      staticLink = pack $ staticRoot ++ "?query=" ++ (rsQuery rs) ++ "%20module%3A" ++ m
+      dynamicLink = pack $ "addToQuery('module:" ++ m ++ "'); return false;"
+
+-- | The aggregated package list.
+packageList :: RenderState -> [(String, Int)] -> XHtml FlowContent
+packageList _ [] = empty
+packageList rs tp = div' [A.id_ "packages"] $ do
+  div' [A.class_ "headline"] $ text "Top 15 Packages"
+  mapM_ topPackage (take 15 tp)
+  where
+  topPackage (k, c) = div' [A.class_ "package"] $ do
+    a' [A.class_ "packageLink", A.href staticLink, onclick dynamicLink] $ text' k
+    text " "
+    span' [A.class_ "packageCount"] $ text' $ show c
+      where
+      staticLink = pack $ staticRoot ++ "?query=" ++ (rsQuery rs) ++ "%20package%3A" ++ k
+      dynamicLink = pack $ "addToQuery('package:" ++ k ++ "'); return false;"
+
+-- | The Hackage hits (i.e. packages)
+packageResults :: RenderState -> (Result PackageInfo) -> XHtml FlowContent
+packageResults rs rp = let pl = extractPackages rp in 
+  if L.null pl then empty else div' [A.id_ "hackage"] $ do 
+    mapM_ packageInfo pl
+  where
+  packageInfo k = div' [A.class_ "package"] $ do
+    div' [A.class_ "category"] $ mapM_ packageCategory (split "," $ p_category k)
+    div' [A.class_ "name"] $ do
+      a' [A.class_ "name", A.href $ pack $ "http://hackage.haskell.org/package/" ++ (p_name k)] $ 
+                                text' (p_name k)
+      span' [A.class_ "synopsis"] $ text' (" " ++ (p_synopsis k))
+    div' [A.class_ "description"] $ text' (p_description k)
+    div' [A.class_ "author"] $ text' (p_author k)
+  packageCategory c = a' [A.class_ "category", A.href $ pack $ "http://hackage.haskell.org/packages/archive/pkg-list.html#cat:" ++ map toLower c] $ text' c
+  extractPackages = take pageLimit . drop (rsStart rs) . 
+                    (mapMaybe (\(_, (di, _)) -> custom $ document di)) . 
+                    reverse . L.sortBy (compare `on` (docScore . fst . snd)) . toListDocIdMap . docHits
+
+-- | The function hits.
+functionResults :: RenderState -> (Result FunctionInfo) -> XHtml FlowContent
+functionResults rs rf = do
+  div' [A.id_ "words"] $ mapM_ (wordInfo rs $ maxScoreWordHits rf) (toListSortedWords $ wordHits rf)
+  div' [A.id_ "documents"] $ table $ mapM_ functionInfo (toListSortedDocs $ docHits rf)
+    where
+                notSignature (_, (_, wch)) = M.keys wch /= ["signature"]
+                toListSortedDocs = take pageLimit . drop (rsStart rs) . reverse . 
+                        L.sortBy (compare `on` (docScore . fst . snd)) . toListDocIdMap
+                toListSortedWords = L.sortBy (compare `on` fst) . take maxWordHits . 
+                        L.sortBy (compare `on` (wordScore .fst . snd)) . filter notSignature . M.toList
+
+-- | Render the information about a function (module w/ link, function name, signature, ...)
+functionInfo :: (DocId, (DocInfo FunctionInfo, DocContextHits)) -> XHtml Table1Content
+functionInfo (_, (DocInfo (Document _ _ Nothing) _, _)) = error "Expecting custom document info"
+functionInfo (_, (DocInfo (Document t u (Just fi)) r, _)) = tbody $ do
+        tr' [A.class_ "function"] $ do
+                td' [A.class_ "module"] $ do
+                        a' [A.class_ "module", A.href $ pack (modLink u)] $ text' (moduleName fi ++ ".")
+                td' [A.class_ "name"] $ do
+                        a' [A.class_ "function", A.href $ pack u, A.title $ pack ("Score: " ++ show r)] $ text' t
+                td' [A.class_ "signature"] $ do
+                        sigDecl (signature fi)
+        tr' [A.class_ "details"] $ do
+                td' [A.class_ "package"] $ do
+                        a' [A.class_ "package", A.href $ pack (pkgLink $ package fi)] $ text' (package fi)
+                td' [A.class_ "description", A.colspan 2] $ div_ $ do
+                        a' [A.class_ "toggleFold", onclick "toggleFold(this);"] $ empty
+                        div' [A.class_ "description"] $ let f = fctDescr fi in
+                                if L.null f then text "No description." else text' f
+                        let c = sourceURI fi in if L.null c then empty else span' [A.class_ "source"] $ do
+                                a' [A.class_ "source", A.href $ pack c] $ text "Source"
+        where
+        modLink = takeWhile ((/=) '#')
+        pkgLink = (++) "http://hackage.haskell.org/package/"
+        sigDecl s'
+                | s' `elem` ["data", "type", "newtype", "class", "module"] = span' [A.class_ "declaration"] $ text' s
+                | otherwise = text' $ replace "->" " -> " s
+                where
+                s = ":: " ++ stringTrim s'
+
+-- | Render a word in the cloud of suggestions
+wordInfo :: RenderState -> Score -> (Word, (WordInfo, WordContextHits)) -> XHtml FlowContent
+wordInfo rs m (w, (WordInfo ts s, c)) = do
+        a' [A.class_ "cloud", A.href staticLink, onclick dynamicLink, A.title $ pack origin] $ do
+                span' [A.class_ $ pack $ "cloud" ++ (show ((round $ weightScore 1 9 m s)::Int))] $ text' w
+        text " "
+        where
+        origin = join ", " $ M.keys c
+        dynamicLink = pack $ "replaceInfQuery('" ++ escape t ++ "','" ++ escape w ++ "'); return false;"
+        staticLink = pack $  staticRoot ++ "?query=" ++ (replace t w qu) ++ "&start=" ++ (show st)
+        weightScore mi ma to v  = ma - ((to - v) / to) * (ma - mi)
+        escape []      = []
+        escape (x:xs)  = if x == '\'' then "\\'" ++ escape xs else x : (escape xs)
+        t = head ts
+        qu = rsQuery rs
+        st = rsStart rs
+
+pager :: RenderState -> Int -> XHtml FlowContent
+pager rs m = if m <= 0 || m < pageLimit then empty else div'[A.id_ "pager"] $ do
+                nav "previous" "<" (_prevPage pg)
+                mapM_ page (_predPages pg)
+                span' [A.class_ "current"] $ text' $ show $  _currPage pg
+                mapM_ page (_succPages pg)
+                nav "next" ">" (_nextPage pg)
+        where
+        pg = makePager (rsStart rs) pageLimit m
+        nav :: Text -> String -> Maybe Int -> XHtml FlowContent
+        nav _ _ Nothing = empty
+        nav c t (Just v) = a' [A.class_ c, A.href $ statLink v, onclick $ dynLink v] $ text' t
+        page (v, t)  = a' [A.class_ "page", A.href $ statLink v, onclick $ dynLink v] $ text' $ show t
+        dynLink v = pack $ "showPage(" ++ show v ++ "); return false;"
+        statLink v = pack $ staticRoot ++ "?query=" ++ (rsQuery rs) ++ "&start=" ++ show v
+
+data Pager  = Pager 
+              { _prevPage  :: Maybe Int -- == last predPages
+              , _predPages :: [(Int, Int)]
+              , _currPage  :: Int
+              , _succPages :: [(Int, Int)]
+              , _nextPage  :: Maybe Int -- == head succPages
+              }
+
+-- Start element (counting from zero), elements per page, total number of elements.
+makePager :: Int -> Int -> Int -> Pager
+makePager s pg n = Pager pv (drop (length pd - 10) pd) (length pd + 1) (take 10 sc) nt
+  where
+  pv = if s < pg then Nothing else Just (s - pg)
+  nt = if s + pg >= n then Nothing else Just (s + pg)
+  pd = map (\x -> (x, x `div` pg + 1)) $ genPred s []
+    where
+    genPred rp tp = let np = rp - pg in
+                                  if np < 0
+                                  then tp
+                                  else genPred np (np:tp)
+  sc = map (\x -> (x, x `div` pg + 1)) $ genSucc s []
+    where
+    genSucc rs ts = let ns = rs + pg in
+                                  if ns >= n
+                                  then ts
+                                  else genSucc ns (ts ++ [ns])
+
+-- | Replace a given list with another list in a list.
+replace :: Eq a => [a] -> [a] -> [a] -> [a]
+replace old new l = L.intercalate new . split old $ l
+
+-- | Remove leading and trailing whitespace using isSpace.
+stringTrim :: String -> String
+stringTrim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
+
diff --git a/src/Hayoo/Search/JSON.hs b/src/Hayoo/Search/JSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/JSON.hs
@@ -0,0 +1,98 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.Search.JSON
+  Copyright  : Copyright (C) 2010 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  Rendering a Hayoo! search result into a JSON string.
+
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Hayoo.Search.JSON
+    ( renderJson
+    , renderEmptyJson
+    )
+where
+
+import qualified Data.Map as M
+
+import Text.JSON
+
+import Holumbus.Index.Common
+import Holumbus.Query.Result
+
+import Hayoo.IndexTypes
+import Hayoo.Search.Common
+
+-- ----------------------------------------------------------------------------
+
+renderEmptyJson         :: String
+renderEmptyJson         = encodeStrict $
+                          jo 
+                          [ ("message", js "Please provide a query.")
+                          , ("hits", showJSON (0 :: Int))
+                          , ("functions", JSArray []) 
+                          , ("completions", JSArray []) 
+                          , ("modules", JSArray []) 
+                          , ("packages", JSArray []) 
+                          ]
+
+renderJson              :: StatusResult -> String
+renderJson (msg, res, _, mods, pkgs)
+                        = encodeStrict $
+                          jo
+                          [ ("message",      js msg)
+                          , ("hits",         showJSON      $ sizeDocHits res)
+                          , ("functions" ,   buildDocHits  $ docHits res)
+                          , ("completions" , buildWordHits $ wordHits res)
+                          , ("modules",      buildTopList    mods)
+                          , ("packages",     buildTopList    pkgs)
+                          ]
+
+buildDocHits            :: DocHits FunctionInfo -> JSValue
+buildDocHits dh         = JSArray $ map buildDoc $ toListDocIdMap dh
+
+buildDoc                :: (DocId, (DocInfo FunctionInfo, DocContextHits)) -> JSValue
+buildDoc (_, (DocInfo (Document t u (Just fi)) _, _))
+                        = jo
+                          [ ("name",        js t)
+                          , ("uri",         js u)
+                          , ("module",      js $ moduleName fi)
+                          , ("signature",   js $ signature fi)
+                          , ("package",     js $ package fi)
+                          , ("description", js $ fctDescr fi)
+                          ]
+buildDoc _              = error "Expected custom function info"
+
+buildWordHits           :: WordHits -> JSValue
+buildWordHits wh        = JSArray $ map buildWord (M.toList wh)
+
+buildWord               :: (Word, (WordInfo, WordContextHits)) -> JSValue
+buildWord (w, (WordInfo _ s, _))
+                        = jo
+                          [ ("word", js w)
+                          , ("count", showJSON s)
+                          ]
+
+buildTopList            :: [(String, Int)] -> JSValue
+buildTopList            = showJSON . map buildTopElem
+  where
+  buildTopElem (n, c)   = jo
+                          [ ("name", js n)
+                          , ("count", showJSON c)
+                          ]
+
+js                      :: String -> JSValue
+js                      = JSString . toJSString
+
+jo                      :: [(String, JSValue)] -> JSValue
+jo                      = JSObject . toJSObject
+
diff --git a/src/Hayoo/Search/Pages/Static.hs b/src/Hayoo/Search/Pages/Static.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/Pages/Static.hs
@@ -0,0 +1,268 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.Search.Pages.Static
+  Copyright  : Copyright (C) 2010 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  The main Hayoo! template.
+-}
+
+-- ----------------------------------------------------------------------------
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Hayoo.Search.Pages.Static (help, about, api, examples) where
+
+import Data.Text (Text)
+
+import Text.XHtmlCombinators
+import qualified Text.XHtmlCombinators.Attributes as A
+
+examples :: XHtml FlowContent
+examples = div' [A.id_ "result"] $ do
+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."
+  div' [A.id_ "words"] $ text " "
+  div' [A.id_ "documents"] $ div' [A.id_ "examples"] $ do
+    text "Hayoo! will search all packages from "
+    a' [A.href "http://hackage.haskell.org"] $ text "Hackage"
+    text ", including all function and type definitions. Here are some example queries:"
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map&apos;); return false;", A.href "hayoo.html?query=map&amp;start=0"] $ text "map"
+      text " searches for everything that contains a word starting with \"map\" (case insensitive) in the function name, module name or description."
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;name:map&apos;); return false;", A.href "hayoo.html?query=name%3Amap&amp;start=0"] $ text "name:map"
+      text " searches for everything where the function name starts with \"map\" (case insensitive)."
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map OR fold&apos;); return false;", A.href "hayoo.html?query=map%20OR%20fold&amp;start=0"] $ text "map OR fold"
+      text " searches for everything that contains a word starting with \"map\" or \"fold\" (case insensitive) in the function name, module name or description."
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map package:containers&apos;); return false;", A.href "hayoo.html?query=map%20package%3Acontainers&amp;start=0"] $ text "map package:containers"
+      text " searches for everything from package \"containers\" that contains a word starting with \"map\" (case insensitive) in the function name, module name or description."
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map hierarchy:Lazy&apos;); return false;", A.href "hayoo.html?query=map%20hierarchy%3ALazy&amp;start=0"] $ text "map hierarchy:Lazy"
+      text " searches for everything where \"Lazy\" appears somewhere in the full qualified module name \
+        \and that contains a word starting with \"map\" (case insensitive) in the function name, module name or description."
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;(map OR fold) module:Data.Map&apos;); return false;", A.href "hayoo.html?query=(map%20OR%20fold)%20module%3AData.Map&amp;start=0"] $ text "(map OR fold) module:Data.Map"
+      text " searches for everything from module \"Data.Map\" that contains a word starting with \"map\" or \"fold\" (case insensitive) in the function name, module name or description."
+    div' [A.class_ "example"] $ p $ do
+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;name:attr module:Text.XML&apos;); return false;", A.href "hayoo.html?query=name%3Aattr%20module%3AText.XML&amp;start=0"] $ text "name:attr module:Text.XML"
+      text " searches for everything from the whole module hierarchy \"Text.XML\" where the function name starts with \"attr\" (case insensitive)."
+
+help :: XHtml FlowContent
+help = div' [A.id_ "result"] $ do
+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."
+
+  div' [A.id_ "helptext", A.class_ "text"] $ do
+    h2 $ text "Basic Usage"
+    p $ do
+      text "By default, Hayoo! searches for function names, module names, signatures and function \
+        \descriptions. With every letter typed, Hayoo! will show the results it thinks are best matching \
+        \the query as well as some suggestions on how the words from the query could be completed. \
+        \Clicking one of these suggestions will replace the according word in the query."
+    p $ do
+      text "Hayoo! displays results as a list of functions, including full qualified module name and the \
+        \function signature. Clicking the function name will lead directly to the corresponding documentation \
+        \while clicking the module name will lead to the documentation of the module. Additionally, Hayoo! \
+        \shows the function description (if available) and provides a link leading directly to the source \
+        \of the function (if available). The description of the function can be expanded by clicking on \
+        \the small '+' sign."
+    p $ do
+      text "Along with the results, Hayoo! shows two lists on the right, containing the top fifteen \
+        \root-modules and packages. These are aggregated from the actual results. Clicking on each of \
+        \these will further restrict the current query to the respective module hierarchy or package. \
+        \On the left side, package search results are shown if the query matches the package information."
+
+    h2 $ text "Advanced Queries"
+    p $ do
+      text "If words are seperated by whitespace, Hayoo! will search for results containing both words. \
+        \Instead of using whitespace, the explicit "
+      span' [A.class_ "query"] $ text "AND"
+      text " operator can be used. Hayoo! also supports "
+      span' [A.class_ "query"] $ text "OR"
+      text " and "
+      span' [A.class_ "query"] $ text "NOT"
+      text " operators, although the "
+      span' [A.class_ "query"] $ text "NOT"
+      text " operator may only be used together with "
+      span' [A.class_ "query"] $ text "AND"
+      text ", e.g. "
+      span' [A.class_ "query"] $ text "map NOT fold"
+      text " or "
+      span' [A.class_ "query"] $ text "map AND NOT fold"
+      text ". Operator precedence can be influenced using round parentheses. Phrases can be searched \
+        \using double quotes, e.g. "
+      span' [A.class_ "query"] $ text "\"this is a phrase\""
+      text "."
+ 
+    p $ do
+      text "It is possible to restrict a search to certain packages or modules. The most simple way would \
+        \be to just include the package name in the search, e.g. "
+      span' [A.class_ "query"] $ text "map base"
+      text " will prefer hits from the base package. But the restriction can also be more explicit, like "
+      span' [A.class_ "query"] $ text "map package:base"
+      text " or like "
+      span' [A.class_ "query"] $ text "map module:data.list"
+      text ". It is also possible to specify several different modules or packages, like this: "
+      span' [A.class_ "query"] $ text "fold module:(data.list OR data.map)"
+      text ". This will return all hits for fold in the module hierarchies below Data.List and Data.Map."
+    p $ do
+      text "Hayoo! always performs fuzzy queries. This means, it tries to find something even if the \
+        \query contains spelling errors. For example, Hayoo! will still find \"fold\" if \"fodl\" is \
+        \being searched. If Hayoo! detects \">\" in the query string, it will only search for signatures. \
+        \A signature query may consist of explicit type names as well as type variables. For example, \
+        \searching for \"a > b\" will find signatures like \"Int > Bool\"."
+
+    h2 $ text "Scope"
+    p $ do
+      text "Currently, Hayoo! searches all packages available on "
+      a' [A.href "http://hackage.haskell.org"] $ text "Hackage"
+      text ". Additionally, any Haskell documentation generated by Haddock can be included in Hayoo!. \
+        \Just send a message including an URI where the documentation can be found to "
+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"
+      text "."
+
+about :: XHtml FlowContent
+about = div' [A.id_ "result"] $ do
+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."
+
+  div' [A.id_ "abouttext", A.class_ "text"] $ do
+    h2 $ text "About Hayoo!"
+    p $ do
+      text "Hayoo! is a search engine specialized on " 
+      a' [A.href "http://www.haskell.org"] $ text "Haskell"
+      text " API documentation. The goal of Hayoo! is to provide an interactive, easy-to-use search interface to \
+        \the documenation of various Haskell packages and libraries. Although the Hayoo! data is regularly updated, \
+        \we might miss a package or library. If you think there is some documentation for Haskell modules available \
+        \on the Internet which should be added to Hayoo!, just drop us a note at "
+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"
+      text " and tell us the location where we can find the documentation."
+
+    h2 $ text "Background"
+    p $ do
+      text "Hayoo! is an example application of the "
+      a' [A.href "http://holumbus.fh-wedel.de"] $ text "Holumbus"
+      text " framework and was heavily inspired by "
+      a' [A.href "http://www.haskell.org/hoogle"] $ text "Hoogle"
+      text ". The Holumbus library provides the search and indexing backend for Hayoo!. Holumbus and Hayoo! \
+        \have been developed by Sebastian M. Gauck and Timo B. Hübel at "
+      a' [A.href "http://www.fh-wedel.de"] $ text "FH Wedel University of Applied Sciences"
+      text ". The Holumbus framework provides the basic building blocks for creating highly customizable search \
+        \engines. To demonstrate the flexibility of the framework by a very special use case, the Hayoo! Haskell \
+        \API search was implemented using Holumbus."
+    p $ do
+      text "Currently, Hayoo! is still in beta stage. This means, it can become unavailable unexpectedly, as \
+        \we do some maintenance or add new features. Therefore you should not yet rely on Hayoo! as primary \
+        \ search engine for Haskell documentation."
+    p $ do
+      text "Hardware infrastructure for daily index updates is generously sponsored by "
+      a' [A.href "http://www.fortytools.com"] $ text "fortytools gmbh"
+      text ", your friendly Haskell web development company."
+
+    h2 $ text "Technical Information"
+    p $ do
+      text "Hayoo! is written entirely in Haskell and consists of two main parts: The indexer, which regularly \
+        \checks Hackage for package updates and builds the search index and the web frontend, which relies on \
+        \Apache, FastCGI and Hack for presenting search results to the user."
+
+    h2 $ text "Feedback"
+    p $ do
+      text "We would like to know what you think about Hayoo!, therefore you can reach us at "
+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"
+      text " and tell us about bugs, suggestions or anything else related to Hayoo!."
+    div' [A.id_ "sponsors"] $ do
+      div' [A.id_ "hol"] $ do
+        a' [A.href "http://holumbus.fh-wedel.de"] $ img' "" "" [A.src "hayoo/hol.png", A.alt "Holumbus logo", A.class_ "logo"] -- Change here when img bug in xhtml-combinators is fixed
+      div' [A.id_ "ft"] $ do
+        a' [A.href "http://www.fortytools.com"] $ img' "" "" [A.src "hayoo/ft.png", A.alt "fortytools logo", A.class_ "logo"]
+      div' [A.id_ "fhw"] $ do
+        a' [A.href "http://www.fh-wedel.de"] $ img' "" "" [A.src "hayoo/fhw.gif", A.alt "FH-Wedel logo", A.class_ "logo"]
+      
+api :: XHtml FlowContent
+api = div' [A.id_ "result"] $ do
+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."
+
+  div' [A.id_ "helptext", A.class_ "text"] $ do
+    h2 $ text "Hayoo! API"
+    p $ do
+      text "Hayoo! provides a JSON-based webservice API, which can be used to retrieve search results in a structured \
+           \format. This allows one to include Hayoo! search functionality in other applications. Arbitrary queries \
+           \can be submitted the same way as they would be entered them into the search box and results are returned \
+           \encoded in JSON format."
+
+    p $ do
+      text "You may use this service for whatever you like and without any limitations, although we would be \
+           \very happy to know about any application that uses the Hayoo! webservice API. Just drop us a line at"
+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"
+      text "."      
+
+    h2 $ text "Request URI"
+    p $ text "Direct your search request to the following URI:"
+    pre $ text "http://holumbus.fh-wedel.de/hayoo/hayoo.json?query=YOUR_QUERY"
+    p $ do
+      text "Provide your query as argument to the "
+      code $ text "query"
+      text " URI parameter. Please note that you need to ensure proper URI encoding for the query argument. The syntax \
+           \for the query is the same as if it would be entered into the search box. A detailed explanation of the \
+           \syntax can be found " 
+      a' [A.href "help.html"] $ text "here"
+      text "."
+
+    h2 $ text "Response"
+    p $ do
+      text "The response to a search request will be encoded in "
+      a' [A.href "http://www.json.org"] $ text "JSON"
+      text " format and is structured as follows:"
+    pre $ do
+      code $ text "{\n\
+                 \  \"message\":\"Found 12 results and 17 completions.\",\n\
+                 \  \"hits\":12,\n\
+                 \  \"functions\":[ {\n\
+                 \    \"name\":\"map\",\n\
+                 \    \"uri\":\"http://hackage.haskell.org/...\",\n\
+                 \    \"module\":\"Data.Map\",\n\
+                 \    \"signature\":\"(a->b)->[a]->[b]\",\n\
+                 \    \"package\":\"containers\"\n\
+                 \  }, ... ],\n\
+                 \  \"completions\":[ {\n\
+                 \    \"word\":\"MapM\",\n\
+                 \    \"count\":11\n\
+                 \  }, ... ],\n\
+                 \  \"modules\":[ {\n\
+                 \    \"name\":\"Data\",\n\
+                 \    \"count\":19\n\
+                 \  }  }, ... ],\n\
+                 \  \"packages\":[ {\n\
+                 \    \"name\":\"containers\",\n\
+                 \    \"count\":13\n\
+                 \  }, ... ]\n\
+                 \}"
+    p $ do
+      (text "The ") >> (ct "message") >> (text " field will contain a descriptive status message about the result \
+        \or any errors encountered. The ") >> (ct "hits") >> (text " field will contain the total number of \
+        \functions found. In the ") >> (ct "functions") >> (text " field, an array containing all functions found \
+        \will be returned. For every function, a JSON object is included in the array.")
+    p $ do
+      (text "Each of these objects contains the function name, the URI pointing to the Haddock documentation, the module, \
+        \the signature and the package name in the ") >> (ct "name") >> (text ", ") >> (ct "uri") >> (text ", ")
+        >> (ct "module") >> (text ", ") >> (ct "signature") >> (text" and ") >> (ct "package") >> (text " fields, respectively.")
+    p $ do
+      (text "The ") >> (ct "completions") >> (text " contains all word completions (suggestions) resulting from the query \
+        \For every completion, a JSON object is included in the array, containing the suggested word and the total number \
+        \of occurrences of this word in the search result in the ") >> (ct "word") >> (text " and ") >> (ct "count")
+        >> (text " fields.")
+    p $ do
+      (text "The ") >> (ct "modules") >> (text " and ") >> (ct "packages") >> (text " fields contain arrays with JSON objects \
+        \denoting the occurrences of root modules and packages in the search result. For each element, the module/package \
+        \name is included in the ") >> (ct "name") >> (text " field and the number of occurrences in the ") >> (ct "count")
+        >> (text " field.")
+
+ct :: (Functor t, Monad t, Inline c) => Text -> XHtmlT t c
+ct = code . text
+
diff --git a/src/Hayoo/Search/Pages/Template.hs b/src/Hayoo/Search/Pages/Template.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/Pages/Template.hs
@@ -0,0 +1,136 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.Search.Pages.Template
+  Copyright  : Copyright (C) 2010 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  The main Hayoo! template.
+-}
+
+-- ----------------------------------------------------------------------------
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Hayoo.Search.Pages.Template (Template, makeTemplate) where
+
+import qualified Data.List as L
+import qualified Data.Text as T
+
+import Control.Category
+
+import Text.XHtmlCombinators
+import qualified Text.XHtmlCombinators.Attributes as A
+
+type Template = XHtml FlowContent -> XHtml Page
+
+-- | Make a Hayoo! template from the given package//function statistics.
+makeTemplate :: Int -> Int -> Template
+makeTemplate np nf template = html True $ do
+  makeHead
+  makeBody np nf template
+
+makeHead :: XHtml TopLevelContent
+makeHead = head_ $ do
+  meta' "text/html; charset=UTF-8" [A.httpEquiv "content-type"]
+  meta' "Haskell, API, Search, Hackage, Functions, Types, Packages" [A.name "keywords"]
+  meta' "A Haskell API search engine with find-as-you-type and suggestions. Searches for function and type defintions in all Haskell packages from Hackage." [A.name "description", A.lang "en"]
+
+  link' [A.type_ "image/ico", A.href "hayoo/favicon.ico", A.rel "Shortcut icon"]
+  link' [A.type_ "text/css", A.href "hayoo/hayoo.css", A.rel "stylesheet"]
+  link' [A.type_ "application/opensearchdescription+xml", A.href "hayoo/opensearch.xml", A.rel "search"]
+
+  title "Hayoo! - Haskell API Search"
+
+  script' "text/javascript" [A.src "hayoo/prototype.js"] "&nbsp;"
+  script' "text/javascript" [A.src "hayoo/hayoo.js"] "&nbsp;"
+
+  -- Piwik tracking stuff
+  script "text/javascript" 
+    "var pkBaseURL = (('https:' == document.location.protocol) ? 'https://piwik.hayoo.info/' : 'http://piwik.hayoo.info/'); \
+    \document.write(unescape(\"%3Cscript src='\" + pkBaseURL + \"piwik.js' type='text/javascript'%3E%3C/script%3E\"));"
+  script "text/javascript"
+    "try {\
+      \var piwikTracker = Piwik.getTracker(pkBaseURL + \"piwik.php\", 3);\
+      \piwikTracker.trackPageView();\
+      \piwikTracker.enableLinkTracking();\
+    \} catch( err ) {}"
+
+makeInfo :: XHtml FlowContent
+makeInfo = div' [A.id_ "info"] $ do
+  a' [A.href "help.html"] $ text "Help"
+  text " | "
+  a' [A.href "about.html"] $ text "About"
+  text " | "
+  a' [A.href "api.html"] $ text "API"
+  text " | "
+  a' [A.href "/blog"] $ text "Blog"
+  text " | "
+  a' [A.href "http://hackage.haskell.org"] $ text "Hackage"
+  text " | "
+  a' [A.href "http://www.haskell.org"] $ text "Haskell"
+
+makeQuery :: Int -> Int -> XHtml FlowContent
+makeQuery np nf = div' [A.id_ "query"] $ do
+  div' [A.id_ "logo"] $ a' [A.href "hayoo.html"] $ img' "" "" [A.src "hayoo/hayoo.png", A.alt "Hayoo! logo", A.class_ "logo"]
+  form' "hayoo.html" [A.id_ "queryform", A.attr "onsubmit" "return forceProcessQuery()", A.method "get"] $ div' [A.id_ "queryinterface"] $ do
+    input' [A.id_ "querytext", A.type_ "text", A.attr "onkeyup" "tryProcessQuery()", A.name "query", A.attr "autocomplete" "off", A.value ""]
+    input' [A.id_ "querybutton", A.type_ "submit", A.value "Search"]
+    img' "" "" [A.src "hayoo/loader.gif", A.alt "Throbber", A.id_ "throbber", A.style "display:none;"]
+    div' [A.class_ "stats"] $ do
+      text $ T.concat ["Concurrently search more than ", T.pack $ showCnt np, " packages and more than ", T.pack $ showCnt nf, " functions!"]
+
+makeCredits :: XHtml FlowContent
+makeCredits = div' [A.id_ "credits"] $ do
+  div' [A.id_ "powered"] $ do
+    div' [A.id_ "libs"] $ do
+      text "Powered by " 
+      a' [A.class_ "credits", A.href "http://www.haskell.org"] $ text "Haskell"
+      text ", "
+      a' [A.class_ "credits", A.href "http://www.fh-wedel.de/~si/HXmlToolbox/"] $ text "HXT"
+      text " and "
+    a' [A.href "http://holumbus.fh-wedel.de"] $ img' "" "" [A.src "hayoo/holumbus.png", A.alt "Holumbus logo", A.class_ "logo"]
+
+  div' [A.id_ "authors"] $ do
+    div' [A.id_ "feedback"] $ do
+      text "Please send any feedback to "
+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"
+    div' [A.id_ "copyright"] $ do
+      text "Hayoo! beta 2.2 © 2010 "
+      span' [A.class_ "author"] $ a' [A.href "http://tbh.github.com"] $ text "Timo B. Hübel"
+      text ", "
+      span' [A.class_ "author"] $ text "Sebastian M. Gauck"
+      text " & "
+      span' [A.class_ "author"] $ text "Uwe Schmidt"
+
+makeBody :: Int -> Int -> XHtml FlowContent -> XHtml TopLevelContent
+makeBody np nf content = body $ do
+  div' [A.id_ "container"] $ do
+    makeInfo
+    makeQuery np nf
+    content
+    makeCredits
+    makeTracking
+
+makeTracking :: XHtml FlowContent
+makeTracking = 
+  noscript $ do
+    p $ do
+      img' "" "" [A.src "http://piwik.hayoo.info/piwik.php?idsite=3", A.alt "", A.style "border:0"]
+
+showCnt         :: Int -> String
+showCnt         = show >>> fmtCnt
+    where
+    fmtCnt      = reverse >>> insDot >>> reverse
+    insDot s
+        | L.null y      = s
+        | otherwise     = x ++ "." ++ insDot y
+        where
+        (x , y) = splitAt 3 s
+
+
diff --git a/src/Hayoo/Search/Parser.hs b/src/Hayoo/Search/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Search/Parser.hs
@@ -0,0 +1,208 @@
+-- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Hayoo.Parser
+  Copyright  : Copyright (C) 2008 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.2
+
+  The parser for the Hayoo! web search.
+ 
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Hayoo.Search.Parser where
+
+-- import qualified Data.Map as M
+-- import Data.Char
+
+import Hayoo.Signature
+
+-- import Holumbus.Utility
+import Holumbus.Query.Language.Grammar
+
+import Text.ParserCombinators.Parsec
+
+-- ------------------------------------------------------------
+
+{- Uwe: done 
+
+-- TODO TH This normalize and strip have to be shared by indexer and search!
+
+-- | Normalizes a Haskell signature, e.g. @String -> Int -> Int@ will be transformed to 
+-- @a->b->b@. All whitespace will be removed from the resulting string.
+normalizeSignature :: String -> String
+normalizeSignature = join "->" . (replaceTypes M.empty ['a'..'z']) . split "->" . filter (not . isSpace)
+  where
+  replaceTypes _ _ [] = []
+  replaceTypes v t (x:xs) = let (nv, ut, rx) = replace' in rx:(replaceTypes nv ut xs)
+    where
+    replace' = let ut = [head t] in maybe (M.insert r ut v, tail t, ut) (\n -> (v, t, n)) (M.lookup r v)
+      where r = stripWith (\c -> (c == '(') || (c == ')')) x
+
+-- | Strip unneeded whitespace from a signature, e.g. @String -> Map k a -> Int@ will be transformed
+-- to @String->Map k a->Int@.
+stripSignature :: String -> String
+stripSignature = sep "->" . lsep "(" . rsep ")" . sep "." . sep "=>"
+  where
+  sep s = join s . map strip . split s
+  lsep s = join s . map stripl . split s
+  rsep s = join s . map stripr . split s
+-}
+
+-- ------------------------------------------------------------
+
+-- | Parse a query using the special Hayoo! syntax.
+parseQuery :: String -> Either String Query
+parseQuery s
+    | isSignature s     = Right sigQuery
+    | otherwise         = result . (parse query "") $ s
+  where
+  sigQuery              = BinQuery Or
+                          (Specifier ["signature"]  (Word $ stripSignature     s))
+                          (Specifier ["normalized"] (Word $ normalizeSignature s))
+
+  result (Left _e)      = Right (FuzzyWord s)
+  result (Right q)      = Right q
+
+-- | A query may always be surrounded by whitespace
+query :: Parser Query
+query = spaces >> andQuery
+-- query = spaces >> ((try sigQuery) <|> (andQuery))
+
+-- | Parse an and query.
+andQuery :: Parser Query
+andQuery = do t <- orQuery
+              try (andOp' t) <|> return t
+  where
+  andOp' r = do andOp
+                q <- (notQuery <|> andQuery)
+                return (BinQuery And r q)
+
+-- | Parse an or query.
+orQuery :: Parser Query
+orQuery = do t <- contextQuery
+             do orOp
+                q <- orQuery
+                return (BinQuery Or t q)
+                <|> return t
+
+-- | Parse a negation.
+notQuery :: Parser Query
+notQuery = do notOp
+              q <- contextQuery
+              return (Negation q)
+
+-- | Parse a context query.
+contextQuery :: Parser Query
+contextQuery = try contextQuery' <|> parQuery
+  where
+  contextQuery' = do c <- contexts
+                     spaces
+                     _ <- char ':'
+                     spaces
+                     t <- parQuery
+                     return (Specifier c t)
+
+-- | Parse a query surrounded by parentheses.
+parQuery :: Parser Query
+parQuery = parQuery' <|> phraseQuery <|> wordQuery
+  where
+  parQuery' = do _ <- char '('
+                 spaces
+                 q <- andQuery
+                 spaces
+                 _ <- char ')'
+                 return q
+
+-- | Parse a phrase query.
+phraseQuery :: Parser Query
+phraseQuery = do p <- phrase
+                 return (Phrase p)
+
+-- | Parse a word query.
+wordQuery :: Parser Query
+wordQuery = do w <- word
+               return (FuzzyWord w)
+{-
+-- | Parse a signature.
+sigQuery :: Parser Query
+sigQuery = do
+           r <- contains "->"
+           s <- return (stripSignature r)
+           n <- return (normalizeSignature r)
+           return $ BinQuery Or (Specifier ["signature"] (Word s)) (Specifier ["normalized"] (Word n))
+-}
+contains :: String -> Parser String
+contains s = do
+             pr <- many1 (noneOf s)
+             _ <- string s
+             po <- many1 anyChar
+             return (pr ++ s ++ po)
+
+-- | Parse an and operator.
+andOp :: Parser ()
+andOp = (try andOp') <|> spaces1
+  where
+  andOp' = do spaces
+              _ <- string "AND" 
+              spaces1
+              return ()
+
+-- | Parse an or operator.
+orOp :: Parser ()
+orOp = try orOp'
+  where
+  orOp' = do spaces
+             _ <- string "OR"
+             spaces1
+             return ()
+
+-- | Parse a not operator.
+notOp :: Parser ()
+notOp = try notOp'
+  where
+  notOp' = do spaces
+              _ <- string "NOT" 
+              spaces1
+              return ()
+
+-- | Parse a word.
+word :: Parser String
+word = many1 wordChar
+
+-- | Parse a phrase.
+phrase :: Parser String
+phrase = do _ <- char '"'
+            p <- many1 phraseChar
+            _ <- char '"'
+            return p
+
+-- | Parse a character of a word.
+wordChar :: Parser Char
+wordChar = noneOf "\")( "
+
+-- | Parse a character of a phrases.
+phraseChar :: Parser Char
+phraseChar = noneOf "\""
+
+-- | Parse a list of contexts.
+contexts :: Parser [String]
+contexts = context `sepBy1` (char ',')
+
+-- | Parse a context.
+context :: Parser String
+context = do spaces 
+             c <- (many1 alphaNum)
+             spaces
+             return c
+
+-- | Parse at least on white space character.
+spaces1 :: Parser ()
+spaces1 = skipMany1 space
+
diff --git a/src/Hayoo/Signature.hs b/src/Hayoo/Signature.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Signature.hs
@@ -0,0 +1,96 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.Signature
+where
+
+import           Control.Arrow
+
+import           Data.List
+
+import           Holumbus.Crawler.Util  ( tokenize
+                                        , match
+                                        , split
+                                        )
+
+-- ------------------------------------------------------------
+
+getSignature                    :: String -> String
+getSignature                    = split "(.*(=>|::))?(\\s)*" >>> snd
+
+-- | Strip redundant whitespace from a signature, e.g. @String -> Map k a -> Int@ will be transformed
+-- to @String->Map k a->Int@.
+
+stripSignature                  :: String -> String
+stripSignature                  = tokenizeSignature >>> joinSignatureTokens
+
+-- | Normalizes a Haskell signature, e.g. @String -> Int -> Int@ will be transformed to 
+-- @a->b->b@. All whitespace will be removed from the resulting string.
+
+normalizeSignature              :: String -> String
+normalizeSignature              = tokenizeSignature >>> normSignatureIds >>> joinSignatureTokens
+
+-- | Tokenize a signature string
+
+tokenizeSignature               :: String -> [String]
+tokenizeSignature               = tokenize sigToken
+    where
+    sigToken                    = "[\\]\\[(,)]|->|=>|\\*|" ++ sigIdent
+
+ascSym                          :: String
+ascSym                          = "[-!#$%&*+./<=>?@\\\\^|~]"
+
+uniSym                          :: String
+uniSym                          = "[\\p{S}\\p{P}-[(),;\\[\\]`{}_:'\"]]"
+
+uniSymNoColon                   :: String
+uniSymNoColon                   = "[\\p{S}\\p{P}-[(),;\\[\\]`{}_:'\"]]"
+
+conSym                          :: String
+conSym                          = ":(" ++ uniSymNoColon ++ "|" ++ uniSym ++ "{2,})"     -- somewhat tricky tricky (::) isn't a conSym
+
+sigIdent                        :: String
+sigIdent                        = "[\\p{L}#][\\p{L}\\{N}'_.]*"
+
+haskIdent                       :: String
+haskIdent                       = "[\\p{L}][\\p{L}\\{N}'_.]*"
+
+varIdent                        :: String
+varIdent                        = "[\\p{Ll}][\\p{L}\\{N}'_.]*"
+
+typeIdent                       :: String
+typeIdent                       = "[\\p{Lu}#][\\p{L}\\{N}'_.]*" -- "|" ++ conSym        -- but this requires latest hxt-8.5.3, beacuse of a parser error in uniSym expression
+
+joinSignatureTokens             :: [String] -> String
+joinSignatureTokens             = mapAccumL insBlank False >>> snd >>> concat
+    where
+    insBlank isId x             = (isId'
+                                  , if isId && isId'
+                                    then ' ' : x
+                                    else       x
+                                  )
+        where
+        isId'                   = match sigIdent x
+
+normSignatureIds                :: [String] -> [String]
+normSignatureIds                = mapAccumL renId ([], ['a'..]) >>> snd
+    where
+    renId ac@(env, ids) x
+        | isId x                = case lookup x env of
+                                  Nothing       -> ( ((x, head ids) : env, tail ids)
+                                                   , [head ids]
+                                                   )
+                                  Just c        -> (ac, [c])
+        | otherwise             = (ac, x)
+        where
+        isId                    = match varIdent
+
+isSignature                     :: String -> Bool
+isSignature                     = match $ anyWord ++ "(->|" ++ tupleType ++ "|" ++ listType ++ ")" ++ anyWord
+    where
+    anyWord                     = "(.|\\n|\\r)*"
+    listType                    = "\\[" ++ anyWord ++ "\\]"
+    tupleType                   = "\\((" ++ anyWord ++ "," ++ anyWord ++ ")?\\)"
+
+-- ------------------------------------------------------------
diff --git a/src/Hayoo/Snap/Application.hs b/src/Hayoo/Snap/Application.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Snap/Application.hs
@@ -0,0 +1,54 @@
+{-
+
+This module defines our application's monad and any application-specific
+information it requires.
+
+-}
+
+module Hayoo.Snap.Application
+  ( Application
+  , ApplicationState(..)
+  , applicationInitializer
+  ) where
+
+import           Snap.Extension
+
+import           Hayoo.Snap.Extension.HayooState
+
+------------------------------------------------------------------------------
+-- | 'Application' is our application's monad. It uses 'SnapExtend' from
+-- 'Snap.Extension' to provide us with an extended 'MonadSnap' making use of
+-- the Heist and Timer Snap extensions.
+type Application = SnapExtend ApplicationState
+
+
+------------------------------------------------------------------------------
+-- | 'ApplicationState' is a record which contains the state needed by the Snap
+-- extensions we're using.  We're using Heist so we can easily render Heist
+-- templates, and Timer simply to illustrate the config loading differences
+-- between development and production modes.
+
+data ApplicationState = ApplicationState
+    { hayooState    :: HayooState
+    }
+
+------------------------------------------------------------------------------
+
+instance HasHayooState ApplicationState where
+    getHayooState     = hayooState
+    setHayooState s a = a { hayooState = s }
+
+
+------------------------------------------------------------------------------
+-- | The 'Initializer' for ApplicationState. For more on 'Initializer's, see
+-- the documentation from the snap package. Briefly, this is used to
+-- generate the 'ApplicationState' needed for our application and will
+-- automatically generate reload\/cleanup actions for us which we don't need
+-- to worry about.
+
+applicationInitializer :: Initializer ApplicationState
+applicationInitializer = do
+    hayoo <- hayooInitializer
+    return $ ApplicationState hayoo
+
+------------------------------------------------------------------------------
diff --git a/src/Hayoo/Snap/Extension/HayooState.hs b/src/Hayoo/Snap/Extension/HayooState.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Snap/Extension/HayooState.hs
@@ -0,0 +1,132 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{-|
+
+'Snap.Extension.Timer.Impl' is an implementation of the 'MonadTimer'
+interface defined in 'Snap.Extension.Timer'.
+
+As always, to use, add 'TimerState' to your application's state, along with an
+instance of 'HasTimerState' for your application's state, making sure to use a
+'timerInitializer' in your application's 'Initializer', and then you're ready to go.
+
+This implementation does not require that your application's monad implement
+interfaces from any other Snap Extension.
+
+-}
+
+module Hayoo.Snap.Extension.HayooState
+  ( HayooState(..)
+  , HasHayooState(..)
+  , MonadHayoo(..)
+  , hayooInitializer
+  ) where
+
+import Control.Monad.Reader
+
+import Hayoo.IndexTypes                 ( buildRankTable )
+import Hayoo.Search.EvalSearch
+import Hayoo.Search.Pages.Template      ( makeTemplate )
+
+import Holumbus.Index.Common
+
+import Snap.Extension
+import Snap.Types
+
+import System.IO                        ( stderr
+                                        , hPutStrLn
+                                        )
+
+------------------------------------------------------------------------------
+-- | Your application's state must include a 'TimerState' in order for your
+-- application to be a 'MonadTimer'.
+
+newtype HayooState = HayooState
+    { getCore :: Core
+    }
+
+------------------------------------------------------------------------------
+-- | For your application's monad to be a 'MonadTimer', your application's
+-- state needs to be an instance of 'HasTimerState'. Minimal complete
+-- definition: 'getTimerState', 'setTimerState'.
+
+class HasHayooState s where
+    getHayooState :: s -> HayooState
+    setHayooState :: HayooState -> s -> s
+
+
+------------------------------------------------------------------------------
+-- | The 'MonadHayoo' type class. Minimal complete definition: 'hayooCore'.
+
+class MonadSnap m => MonadHayoo m where
+    -- | The core Hayoo state which was last loaded.
+    hayooCore :: m Core
+
+------------------------------------------------------------------------------
+
+instance HasHayooState s => MonadHayoo (SnapExtend s) where
+    hayooCore = fmap getCore $ asks getHayooState
+
+------------------------------------------------------------------------------
+
+instance (MonadSnap m, HasHayooState s) => MonadHayoo (ReaderT s m) where
+    hayooCore = fmap getCore $ asks getHayooState
+
+------------------------------------------------------------------------------
+
+instance InitializerState HayooState where
+    extensionId = const "Hayoo/HayooState"
+    mkCleanup   = const $ return ()
+    mkReload    = const $ return ()
+
+------------------------------------------------------------------------------
+-- | The Initializer for 'HayooState'. No arguments are required.
+
+hayooInitializer :: Initializer HayooState
+hayooInitializer = liftIO getHayooInitialState >>= mkInitializer . HayooState
+
+------------------------------------------------------------------------------
+
+ixBase          :: FilePath
+ixBase          = "./lib"
+
+getHayooInitialState    :: IO Core
+getHayooInitialState
+  = do
+    idx  <- loadIndex     hayooIndex
+    infoM "Hayoo.Main" ("Hayoo index   loaded from file " ++ show hayooIndex)
+
+    doc  <- loadDocuments hayooDocs
+    infoM "Hayoo.Main" ("Hayoo docs    loaded from file " ++ show hayooDocs )
+    infoM "Hayoo.Main" ("Hayoo docs contains " ++ show (sizeDocs doc) ++ " functions and types")
+
+    pidx <- loadIndex     hackageIndex
+    infoM "Hayoo.Main" ("Hackage index loaded from file " ++ show hackageIndex)
+
+    pdoc <- loadPkgDocs   hackageDocs
+    infoM "Hayoo.Main" ("Hackage docs  loaded from file " ++ show hackageDocs)
+    infoM "Hayoo.Main" ("Hackage docs contains " ++ show (sizeDocs pdoc) ++ " packages")
+
+    prnk <- return $ buildRankTable pdoc
+    infoM "Hayoo.Main" ("Hackage package rank table computed")
+
+    tpl  <- return $ makeTemplate (sizeDocs pdoc) (sizeDocs doc)
+
+    return $ Core
+             { index      = idx
+             , documents  = doc
+             , pkgIndex   = pidx
+             , pkgDocs    = pdoc
+             , template   = tpl
+             , packRank   = prnk
+             }
+  where
+  hayooIndex      = ixBase ++ "/ix.bin.idx"
+  hayooDocs       = ixBase ++ "/ix.bin.doc"
+  hackageIndex    = ixBase ++ "/pkg.bin.idx"
+  hackageDocs     = ixBase ++ "/pkg.bin.doc"
+
+  infoM m msg     = hPutStrLn stderr $ m ++ ": " ++ msg
+
+------------------------------------------------------------------------------
+
+
diff --git a/src/Hayoo/Snap/Site.hs b/src/Hayoo/Snap/Site.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/Snap/Site.hs
@@ -0,0 +1,173 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+------------------------------------------------------------------------------
+
+{-|
+
+This is where all the routes and handlers are defined for your site. The
+'site' function combines everything together and is exported by this module.
+
+-}
+
+------------------------------------------------------------------------------
+
+module Hayoo.Snap.Site
+  ( site
+  ) where
+
+import           Control.Applicative
+
+import qualified Data.ByteString.Char8          as C
+import qualified Data.Map                       as M
+import           Data.Maybe
+import qualified Data.Text                      as T
+import qualified Data.Text.Encoding             as T
+
+import           Hayoo.Search.EvalSearch        ( Core
+                                                , template
+                                                , getValDef
+                                                , readDef
+
+                                                , parseQuery
+                                                , genResult
+                                                , emptyRes
+
+                                                , renderJson
+                                                , renderEmptyJson
+
+                                                , examples
+                                                , filterStatusResult
+                                                )
+import           Hayoo.Search.HTML              ( RenderState(..)
+                                                , result
+                                                )
+import qualified Hayoo.Search.Pages.Static      as P
+
+import           Hayoo.Snap.Extension.HayooState
+import           Hayoo.Snap.Application
+
+import           System.FilePath                ( (</>) )
+
+import           Snap.Util.FileServe
+import           Snap.Types
+
+import qualified Text.XHtmlCombinators          as X
+
+
+------------------------------------------------------------------------------
+-- | The main entry point handler.
+
+site :: Application ()
+site = route [ ("/",             ifTop hayooHtml)       -- map to /hayoo.html
+             , ("/hayoo.html",   hayooHtml)
+             , ("/hayoo.json",   hayooJson)
+             , ("/help.html",    serveStatic P.help)
+             , ("/about.html",   serveStatic P.about)
+             , ("/api.html",     serveStatic P.api)
+             , ("/hayoo/:stuff", serveHayooStatic)
+             ]
+       <|> serveDirectory "hayoo"
+
+------------------------------------------------------------------------------
+-- | Deliver Hayoo files
+
+serveHayooStatic :: Application ()
+serveHayooStatic = do
+    relPath <- decodedParam "stuff"
+    serveFile $ "hayoo" </> C.unpack relPath
+  where
+    decodedParam p = fromMaybe "" <$> getParam p
+
+------------------------------------------------------------------------------
+-- | Deliver static Hayoo pages
+
+serveStatic :: X.XHtml X.FlowContent -> Application ()
+serveStatic pg
+  = do
+    core <- hayooCore 
+    putResponse htmlResponse
+    writeText (X.render $ (template core) pg) 
+
+------------------------------------------------------------------------------
+-- | Render JSON page
+
+hayooJson :: Application ()
+hayooJson = do
+    pars <- getParams
+    core <- hayooCore
+    putResponse myResponse
+    writeText (T.pack $ evalJsonQuery (toStringMap pars) core)
+  where
+  myResponse = setContentType "application/json; charset=utf-8"
+               . setResponseCode 200
+               $ emptyResponse
+  toStringMap   = map (uncurry tos) . M.toList
+      where
+      tos k a   = (            T.unpack . T.decodeUtf8  $ k
+                  , concatMap (T.unpack . T.decodeUtf8) $ a
+                  )
+
+evalJsonQuery :: [(String, String)] -> Core -> String
+evalJsonQuery p idct
+    | null request      = renderEmptyJson
+    | otherwise         = renderResult
+    where
+    request =               getValDef p "query"  ""
+
+    {- not used for json output
+    start   = readDef 0    (getValDef p "start"  "")
+    static  = readDef True (getValDef p "static" "")
+    tmpl    = template idct
+    -}
+
+    renderResult        = renderJson
+                          . either emptyRes (genResult idct)
+                          . parseQuery
+                          $ request
+
+------------------------------------------------------------------------------
+-- | Render HTML page
+
+hayooHtml :: Application ()
+hayooHtml = do
+    pars <- getParams
+    core <- hayooCore
+    putResponse htmlResponse
+    writeText $ evalHtmlQuery (toStringMap pars) core
+  where
+  toStringMap   = map (uncurry tos) . M.toList
+      where
+      tos k a   = (            T.unpack . T.decodeUtf8  $ k
+                  , concatMap (T.unpack . T.decodeUtf8) $ a
+                  )
+
+htmlResponse :: Response
+htmlResponse = setContentType "text/html; charset=utf-8"
+               . setResponseCode 200
+               $ emptyResponse
+
+evalHtmlQuery :: [(String, String)] -> Core -> T.Text
+evalHtmlQuery p idct
+    | null request      = renderEmptyHtml
+    | otherwise         = renderResult
+    where
+    request =               getValDef p "query"  ""
+    start   = readDef 0    (getValDef p "start"  "")
+    static  = readDef True (getValDef p "static" "")
+    tmpl    = template idct
+
+    renderEmptyHtml     = X.render $ tmpl examples
+
+    renderResult        = applyTemplate (RenderState request start static)
+                          . filterStatusResult request
+                          . either emptyRes (genResult idct)
+                          . parseQuery
+                          $ request
+        where
+        applyTemplate rs sr
+            | rsStatic rs       = X.render $ tmpl rr
+            | otherwise         = X.render $      rr
+            where
+            rr                  = result rs sr
+
+------------------------------------------------------------------------------
diff --git a/src/Hayoo/URIConfig.hs b/src/Hayoo/URIConfig.hs
new file mode 100644
--- /dev/null
+++ b/src/Hayoo/URIConfig.hs
@@ -0,0 +1,147 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Hayoo.URIConfig
+    ( hayooStart
+    , hayooRefs
+    , hayooGetPackage
+    , hayooPackageNames
+
+    , hackagePackages
+    , hackageStart
+    , isHaddockURI
+
+    , editLatestPackage
+
+    , packageVersion
+    , packageVersion'
+    , packageVersion''
+
+    , fileName
+    )
+where
+
+-- import           Control.Applicative
+
+import           Data.List
+
+import           Holumbus.Crawler
+
+import           Text.XML.HXT.Core
+
+-- ------------------------------------------------------------
+
+hayooStart                      :: [URI]
+hayooStart                      = hackageStart
+
+hayooRefs                       :: Bool -> [String] -> URI -> Bool
+hayooRefs                       = hackageRefs
+
+hayooGetPackage                 :: String -> String
+hayooGetPackage                 = hackageGetPackage
+
+hackageHome                     :: String
+hackageHome                     = "http://hackage.haskell.org/packages/"
+
+hackagePackages                 :: String
+hackagePackages                 = "http://hackage.haskell.org/package/"         -- no "s" at the end !!!
+
+hackageStartPage                :: URI
+hackageStartPage                =  hackageHome ++ "archive/pkg-list.html"
+
+hackageStart                    :: [URI]
+hackageStart                    =  [ hackageStartPage ]
+
+hackageRefs                     :: Bool -> [String] -> URI -> Bool
+hackageRefs withDoc pkgs        = simpleFollowRef'
+                                  ( (hackagePackages ++ packageName')
+                                    : ( if withDoc
+                                        then [ packageDocPath  ++ modulePath ++ ext "html" ]
+                                        else []
+                                      )
+                                  )
+                                  [ packageDocPath ++ alternatives
+                                                       [ "doc-index.*" ++ ext "html"            -- no index files
+                                                       , "src/.*"                               -- no hscolored sources
+                                                       ]
+                                  , hackagePackages ++ packageName' ++ packageVersion''         -- no package pages with (old) version numbers
+                                  ]
+    where
+    packageDocPath              = hackagePackageDocPath ++ packageName' ++ "/" ++ packageVersion' ++ "/doc/html/"
+
+    packageName'
+        | null pkgs             = fileName
+        | otherwise             = alternatives pkgs
+
+hackagePackageDocPath           :: String
+hackagePackageDocPath           = hackageHome ++ "archive/"
+
+hackageGetPackage               :: String -> String
+hackageGetPackage u
+    | hackagePackageDocPath `isPrefixOf` u
+                                = takeWhile (/= '/') . drop (length hackagePackageDocPath) $ u
+    | otherwise                 = ""
+
+getHackagePackage               :: String -> String
+getHackagePackage s
+    | match (hackagePackages ++ packageName) s
+                                = drop (length hackagePackages) s
+    | otherwise                 = ""
+
+isHaddockURI                    :: URI -> Bool
+isHaddockURI                    = match (hackagePackageDocPath ++ fileName ++ "/.+/doc/html/.+[.]html")
+
+-- ------------------------------------------------------------
+
+-- common R.E.s
+
+alternatives                    :: [String] -> String
+alternatives                    = ("(" ++) . (++ ")") . intercalate "|"
+
+moduleName                      :: String
+moduleName                      = "[A-Z][A-Za-z0-9_]*"
+
+modulePath                      :: String
+modulePath                      = moduleName ++ "(-" ++ moduleName ++ ")*"
+
+fileName                        :: String
+fileName                        = "[^/?]+"
+
+ext                             :: String -> String
+ext                             = ("[.]" ++)
+
+packageName                     :: String
+packageName                     = "[A-Za-z]([A-Za-z0-9_]|-)*"
+
+packageVersion
+  , packageVersion'
+  , packageVersion''            :: String
+
+packageVersion                  = "[0-9]+([.][0-9]+)*"          -- there are package versions without sub version no
+packageVersion''                = "-" ++ packageVersion
+packageVersion'                 = alternatives [packageVersion, "latest"]
+
+-- ------------------------------------------------------------
+
+-- In the package doc URIs the package version number "/*.*.*/" is substituted by the alias "/latest/"
+
+editLatestPackage               :: String -> String
+editLatestPackage               = sed (const "/latest/") "/[0-9.]+/"
+
+-- ------------------------------------------------------------
+
+hayooPackageNames               :: SysConfigList -> IOSArrow b String
+hayooPackageNames crawlPars     = ( ( readDocument crawlPars hackageStartPage
+                                      >>>
+                                      getHtmlReferences
+                                      >>>
+                                      arr getHackagePackage
+                                      >>>
+                                      isA (not . null)
+                                    )
+                                  )
+                                  >>. (sort >>> nub)
+
+-- ------------------------------------------------------------
+
diff --git a/src/HayooIndexer.hs b/src/HayooIndexer.hs
new file mode 100644
--- /dev/null
+++ b/src/HayooIndexer.hs
@@ -0,0 +1,745 @@
+{-# OPTIONS #-}
+
+-- ------------------------------------------------------------
+
+module Main (main)
+where
+
+import           Codec.Compression.BZip ( compress, decompress )
+
+import           Control.DeepSeq
+-- import           Control.Monad
+import           Control.Monad.Reader
+
+import qualified Data.Binary                    as B
+import           Data.Char
+import           Data.Function.Selector
+import           Data.Maybe
+
+import           Hayoo.HackagePackage
+import           Hayoo.Haddock
+import           Hayoo.IndexConfig
+import           Hayoo.IndexTypes
+import           Hayoo.PackageArchive
+import           Hayoo.URIConfig
+
+import           Holumbus.Crawler
+import           Holumbus.Crawler.CacheCore
+import           Holumbus.Crawler.IndexerCore
+
+import           System.Console.GetOpt
+import           System.Environment
+import           System.Exit
+import           System.IO
+
+import           Text.XML.HXT.Core
+import           Text.XML.HXT.Cache
+import           Text.XML.HXT.HTTP()
+import           Text.XML.HXT.Curl
+
+-- ------------------------------------------------------------
+
+data AppAction
+    = BuildIx | UpdatePkg | RemovePkg | BuildCache | MergeIx
+      deriving (Eq, Show)
+
+data AppOpts
+    = AO
+      { ao_progname :: String
+      , ao_index    :: String
+      , ao_ixout    :: String
+      , ao_ixsearch :: String
+      , ao_xml      :: String
+      , ao_help     :: Bool
+      , ao_pkgIndex :: Bool
+      , ao_action   :: AppAction
+      , ao_defrag   :: Bool
+      , ao_partix   :: Bool
+      , ao_resume   :: Maybe String
+      , ao_packages :: [String]
+      , ao_latest   :: Maybe Int
+      , ao_getHack  :: Bool
+      , ao_pkgRank  :: Bool
+      , ao_msg      :: String
+      , ao_crawlDoc :: (Int, Int, Int)
+      , ao_crawlSav :: Int
+      , ao_crawlSfn :: String
+      , ao_crawlLog :: (Priority, Priority)
+      , ao_crawlPar :: SysConfig
+      , ao_crawlFct :: HayooIndexerConfig    -> HayooIndexerConfig
+      , ao_crawlPkg :: HayooPkgIndexerConfig -> HayooPkgIndexerConfig
+      , ao_crawlCch :: CacheCrawlerConfig    -> CacheCrawlerConfig
+      }
+
+-- ------------------------------------------------------------
+
+initAppOpts :: AppOpts
+initAppOpts
+    = AO
+      { ao_progname = "hayooIndexer"
+      , ao_index    = ""
+      , ao_ixout    = ""
+      , ao_ixsearch = ""
+      , ao_xml      = ""
+      , ao_help     = False
+      , ao_pkgIndex = False
+      , ao_action   = BuildIx
+      , ao_defrag   = False
+      , ao_partix   = False
+      , ao_resume   = Nothing
+      , ao_packages = []
+      , ao_latest   = Nothing
+      , ao_getHack  = False
+      , ao_pkgRank  = False
+      , ao_msg      = ""
+      , ao_crawlDoc = (25000, 1024, 1)                                          -- max docs, max par docs, max threads: no parallel threads, but 1024 docs are indexed before results are inserted
+      , ao_crawlSav = 5000                                                      -- save intervall
+      , ao_crawlSfn = "./tmp/ix-"                                               -- save path
+      , ao_crawlLog = (DEBUG, NOTICE)                                           -- log cache and hxt
+      , ao_crawlPar = withCache' (60 * 60 * 24 * 30)                            -- set cache dir, cache remains valid 1 month, 404 pages are cached
+                      >>>
+                      withCompression (compress, decompress)                    -- compress cache files
+                      >>>
+                      withStrictDeserialize yes                                 -- strict input of cache files
+                      >>>
+                      withAcceptedMimeTypes [ text_html
+                                            , application_xhtml
+                                            ]
+                      >>>
+                      withCurl [ (curl_location,             v_1)               -- automatically follow redirects
+                               , (curl_max_redirects,        "3")               -- but limit # of redirects to 3
+                               ]
+                      >>>
+                      -- withHTTP [ (curl_max_redirects,        "3") ]          -- nice try: HTTP web access instead of curl, problem: no document size limit
+                      -- >>>
+                      withRedirect yes
+                      >>>
+                      withInputOption curl_max_filesize
+                                          (show (1024 * 1024 * 3 `div` 2 ::Int)) -- this limit excludes automtically generated pages, sometimes > 1.5 Mbyte
+                      >>>
+                      withParseHTML no
+                      >>>
+                      withParseByMimeType yes
+
+      , ao_crawlFct = ( editPackageURIs                                         -- configure URI rewriting
+                        >>>
+                        disableRobotsTxt                                        -- for hayoo robots.txt is not needed
+                      )
+
+      , ao_crawlPkg = disableRobotsTxt
+
+      , ao_crawlCch = ( editPackageURIs                                         -- configure URI rewriting
+                        >>>
+                        disableRobotsTxt                                        -- for hayoo robots.txt is not needed
+                      )
+      }
+    where
+      editPackageURIs
+          = chgS theProcessRefs (>>> arr editLatestPackage)
+
+withCache' :: Int -> XIOSysState -> XIOSysState
+withCache' sec
+    = withCache "./cache" sec yes
+
+-- ------------------------------------------------------------
+
+type HIO = ReaderT AppOpts IO
+
+main :: IO ()
+main
+    = do pn   <- getProgName
+         args <- getArgs
+         runReaderT main2 (evalOptions pn args)
+
+-- ------------------------------------------------------------
+
+main2 :: HIO ()
+main2
+    = do (h, pn) <- asks (ao_help &&& ao_progname)
+         if h
+            then do msg <- asks ao_msg
+                    liftIO $ do hPutStrLn stderr (msg ++ "\n" ++ usageInfo pn hayooOptDescr)
+                                if null msg
+                                   then exitSuccess
+                                   else exitFailure
+            else do asks (snd . ao_crawlLog) >>= setLogLevel ""
+                    a <- asks ao_action
+                    case a of
+                      BuildCache -> mainCache
+                      MergeIx    -> mainHaddock
+                      _          -> do p <- asks ao_pkgIndex
+                                       if p
+                                          then mainHackage
+                                          else mainHaddock
+                    liftIO $ exitSuccess
+
+-- ------------------------------------------------------------
+
+mainCache :: HIO ()
+mainCache
+    = do action
+    where
+      action
+          = asks ao_latest >>=
+            maybe action2 updatePackages
+      action2
+          = do pl <- asks ao_packages
+               case pl of
+                 [] -> action3
+                 _  -> updatePkg pl
+
+      action3
+          = do rs <- asks ao_resume
+               notice $ if isJust rs
+                        then ["resume cache update"]
+                        else ["cache hayoo pages"]
+               hayooCacher >>= writeResults
+
+      updatePackages latest
+          = do notice ["compute list of latest packages"]
+               (hk, cp) <- asks (ao_getHack &&& ao_crawlPar)
+               pl       <- liftIO $ getNewPackages hk latest
+               local (\ opts -> opts { ao_latest   = Nothing
+                                     , ao_crawlPar = setDocAge 1 cp    -- force cache update
+                                     }
+                     ) $ updatePkg pl
+
+      updatePkg []
+          = notice ["no packages to be updated"]
+      updatePkg ps
+          = do notice $ "updating cache with packages:" : ps
+               res  <- hayooPackageUpdate ps
+               writeResults res
+               notice $ "updating cache with latest packages done" : []
+
+-- ------------------------------------------------------------
+
+mainHackage :: HIO ()
+mainHackage
+    = action
+    where
+      action
+          = do apl <- asks (ao_action &&& ao_packages)
+               case apl of
+                 (RemovePkg, [])
+                     -> noaction
+                 (RemovePkg, ps)
+                     -> removePkg ps >>= writeRes
+                 (UpdatePkg, [])
+                     -> noaction
+                 (UpdatePkg, ps)
+                     -> updatePkg ps >>= writeRes
+                 (_,         ps)
+                     -> do rs <- asks ao_resume
+                           if isJust rs
+                              then notice ["resume hackage package description indexing"]
+                              else return ()
+                           indexPkg ps >>= writeRes
+
+      removePkg :: [String] -> HIO HayooPkgIndexerState
+      removePkg ps
+          = do notice $ "removing packages from hackage package index:" : ps
+               res <- removePackagesPkg
+               rnf res `seq`
+                   notice $ "packages removed from hackage package index : " : ps
+               return res
+
+      updatePkg :: [String] -> HIO HayooPkgIndexerState
+      updatePkg ps
+          = do notice $ "updating packages from hackage package index:" : ps
+               newix <- local (\ opts -> opts { ao_action  = BuildIx
+                                              , ao_pkgRank = False
+                                              }
+                              ) (indexPkg ps)
+               oldix <- removePkg ps
+               mergePkg newix oldix
+
+      indexPkg :: [String] -> HIO HayooPkgIndexerState
+      indexPkg ps
+          = do notice $ if null ps
+                          then ["indexing all packages from hackage package index"]
+                          else "indexing hackage package descriptions for packages:" : ps
+               (getS theResultAccu `fmap` hayooPkgIndexer) >>= rankPkg
+
+      rankPkg ix
+          = do rank <- asks ao_pkgRank
+               if rank
+                  then do notice ["computing package ranks"]
+                          let res = packageRanking ix
+                          rnf res `seq`
+                              notice ["package rank computation finished"]
+                          return res
+                  else do notice ["no package ranks computed"]
+                          return ix
+
+mainHaddock :: HIO ()
+mainHaddock
+    = do action
+    where
+      action
+          = do latest <- asks ao_latest
+               maybe action2 updateLatest latest
+      action2
+          = do apl <- asks (ao_action &&& ao_packages)
+               case apl of
+                 (RemovePkg, [])
+                     -> noaction
+                 (RemovePkg, ps)
+                     -> removePkg ps >>= writeRes
+                 (UpdatePkg, [])
+                     -> noaction
+                 (UpdatePkg, ps)
+                     -> updatePkg ps >>= writeRes
+                 (MergeIx, _)
+                     -> loadPartialIx >>= mergeAndWritePartialRes
+                 (_,         ps)
+                     -> do rs <- asks ao_resume
+                           if isJust rs
+                              then notice ["resume haddock document indexing"]
+                              else return ()
+                           indexPkg ps >>= writePartialRes
+      removePkg ps
+          = do notice $ "deleting packages" : ps ++ ["from haddock index"]
+               res <- removePackagesIx
+               rnf res `seq`
+                   notice $ "packages " : ps ++ ["deleted from haddock index"]
+               return res
+
+      updatePkg :: [String] -> HIO (HayooIndexerState)
+      updatePkg ps
+          = do notice $ "updating haddock index with packages:" : ps
+               newix <- local (\ opts -> opts { ao_action  = BuildIx
+                                              }
+                              ) (fst `fmap` indexPkg ps)
+               oldix <- removePkg ps
+               mergePkg newix oldix
+
+      loadPartialIx :: HIO [Int]
+      loadPartialIx
+          = local (\ o -> o { ao_action   = BuildIx
+                            , ao_packages = []
+                            }) (snd `fmap` indexPkg [])
+
+      indexPkg :: [String] -> HIO (HayooIndexerState, [Int])
+      indexPkg ps
+          = do notice $ if null ps
+                          then ["indexing all haddock pages"]
+                          else "indexing haddock for packages:" : ps
+               (getS (theResultAccu .&&&. theListOfDocsSaved) `fmap` hayooIndexer)
+
+      updateLatest latest
+          = do notice ["reindex with latest packages"]
+               hk  <- asks ao_getHack
+               ps  <- liftIO $ getNewPackages hk latest
+               if null ps
+                  then notice ["no new packages to be indexed"]
+                  else do res <- local (\ o -> o { ao_latest = Nothing }
+                                       ) $ updatePkg ps
+                          notice ["reindex with latest packages finished"]
+                          writeRes res
+
+-- ------------------------------------------------------------
+
+noaction :: HIO ()
+noaction
+    = notice ["no packages to be processed"]
+
+-- ------------------------------------------------------------
+
+removePacks :: (B.Binary di, NFData di) =>
+                   (Document di -> String) -> HIO (HolumbusState di)
+removePacks getPkgName'
+    = do (ix, (pkg, dfg)) <- asks (ao_index &&& ao_packages &&& ao_defrag)
+         liftIO $ removePackages' getPkgName' ix pkg dfg
+
+removePackagesIx ::HIO HayooIndexerState 
+removePackagesIx
+    = removePacks getPkgNameFct
+
+removePackagesPkg :: HIO HayooPkgIndexerState 
+removePackagesPkg
+    = removePacks getPkgNamePkg
+
+-- ------------------------------------------------------------
+
+mergePkg :: (B.Binary a) => HolumbusState a -> HolumbusState a -> HIO (HolumbusState a)
+mergePkg nix oix
+    = do notice $ ["merging existing index with new packages"]
+         liftIO $ unionIndexerStatesM oix nix
+
+-- ------------------------------------------------------------
+
+writePartialRes :: (HayooIndexerState, [Int]) -> HIO ()
+writePartialRes (x, ps)
+    = do part <- asks ao_partix
+         if part
+            then mergeAndWritePartialRes ps
+            else writeRes x
+
+mergeAndWritePartialRes :: [Int] -> HIO ()
+mergeAndWritePartialRes ps
+    = do pxs <- (\ fn -> map (mkTmpFile 10 fn) ps) `fmap` asks ao_crawlSfn
+         out <- asks ao_ixsearch
+         mergeAndWritePartialRes' id' pxs out
+    where
+      id' :: SmallDocuments FunctionInfo -> SmallDocuments FunctionInfo
+      id' = id
+
+-- ------------------------------------------------------------
+
+writeRes :: (XmlPickler a, B.Binary a) => HolumbusState a -> HIO ()
+writeRes x
+    = writeSearchBin' x >> writeResults x
+    where
+      writeSearchBin' s
+          = do out <- asks ao_ixsearch
+               writeSearchBin out s
+
+writeResults :: (XmlPickler a, B.Binary a) => a -> HIO ()
+writeResults v
+    = do (xf, of') <- asks (ao_xml &&& (ao_ixout &&& ao_index))
+         writeXml xf  v
+         writeBin (out of') v
+    where
+      out (bf, bi)
+          | null bf     = bi
+          | otherwise   = bf
+      
+-- ------------------------------------------------------------
+
+hayooCacher :: HIO CacheCrawlerState
+hayooCacher
+    = do o <- ask
+         liftIO $ stdCacher
+                    (ao_crawlDoc o)
+                    (ao_crawlSav o, ao_crawlSfn o)
+                    (ao_crawlLog o)
+                    (ao_crawlPar o)
+                    (ao_crawlCch o)
+                    (ao_resume o)
+                    hayooStart
+                    (hayooRefs True [])
+
+-- ------------------------------------------------------------
+
+hayooPackageUpdate :: [String] -> HIO CacheCrawlerState
+hayooPackageUpdate pkgs
+    = do o <- ask
+         liftIO $ stdCacher
+                    (ao_crawlDoc o)
+                    (ao_crawlSav o, ao_crawlSfn o)
+                    (ao_crawlLog o)
+                    (ao_crawlPar o)
+                    -- (setDocAge 1 (ao_crawlPar o))              -- cache validation initiated (1 sec valid) 
+                    (ao_crawlCch o)
+                    Nothing
+                    hayooStart
+                    (hayooRefs True pkgs)
+
+-- ------------------------------------------------------------
+
+hayooPkgIndexer :: HIO HayooPkgIndexerCrawlerState
+hayooPkgIndexer
+    = do o <- ask
+         liftIO $ stdIndexer
+                    (config o)
+                    (ao_resume o)
+                    hackageStart
+                    emptyHolumbusState
+    where
+    config0 o
+        = indexCrawlerConfig
+          (ao_crawlPar o)
+          (hayooRefs False $ ao_packages o)
+          Nothing
+          (Just $ checkDocumentStatus >>> preparePkg)
+          (Just $ hayooGetPkgTitle)
+          (Just $ hayooGetPkgInfo)
+          hayooPkgIndexContextConfig
+
+    config o
+        = ao_crawlPkg o $
+          setCrawlerTraceLevel ct ht   $
+          setCrawlerSaveConf si sp     $
+          setCrawlerMaxDocs md mp mt   $
+          config0                      $ o
+        where
+          (ct, ht)      = ao_crawlLog o
+          si            = ao_crawlSav o
+          sp            = ao_crawlSfn o
+          (md, mp, mt)  = ao_crawlDoc o
+
+-- ------------------------------------------------------------
+
+hayooIndexer :: HIO HayooIndexerCrawlerState
+hayooIndexer
+    = do o <- ask
+         liftIO $ stdIndexer
+                    (config o)
+                    (ao_resume o)
+                    hayooStart
+                    emptyHolumbusState
+    where
+    config0 o
+        = indexCrawlerConfig
+          (ao_crawlPar o)
+          (hayooRefs True $ ao_packages o)
+          Nothing
+          (Just $ checkDocumentStatus >>> prepareHaddock)
+          (Just $ hayooGetTitle)
+          (Just $ hayooGetFctInfo)
+          hayooIndexContextConfig
+
+    config o
+        = ao_crawlFct o $
+          setCrawlerTraceLevel ct ht $
+          setCrawlerSaveConf si sp   $
+          setCrawlerSaveAction partA $
+          setCrawlerMaxDocs md mp mt $
+                                  -- haddock pages don't need to be scanned for new URIs
+          setCrawlerPreRefsFilter noHaddockPage $
+          config0                    $ o
+        where
+          xout              = ao_xml      o
+          (ct, ht)          = ao_crawlLog o
+          si                = ao_crawlSav o
+          sp                = ao_crawlSfn o
+          (md, mp, mt)      = ao_crawlDoc o
+          partA
+              | ao_partix o = writePartialIndex (not . null $ xout)
+              | otherwise   = const $ return ()
+
+noHaddockPage :: IOSArrow XmlTree XmlTree
+noHaddockPage
+    = fromLA $
+      hasAttrValue transferURI (not . isHaddockURI) `guards` this
+
+-- ------------------------------------------------------------
+
+notice :: MonadIO m => [String] -> m ()
+notice = noticeC "hayoo"
+
+-- ------------------------------------------------------------
+
+evalOptions :: String -> [String] -> AppOpts
+evalOptions pn args
+    = foldl (.) (ef1 . ef2) opts $ initAppOpts { ao_progname = pn }
+    where
+    (opts, ns, es)   = getOpt Permute hayooOptDescr args
+    ef1
+        | null es    = id
+        | otherwise  = \ x -> x { ao_help   = True
+                                , ao_msg = concat es
+                                }
+        | otherwise  = id
+    ef2
+        | null ns    = id
+        | otherwise  = \ x -> x { ao_help   = True
+                                , ao_msg = "wrong program arguments: " ++ unwords ns
+                                }
+
+-- ------------------------------------------------------------
+
+hayooOptDescr :: [OptDescr (AppOpts -> AppOpts)]
+hayooOptDescr
+    = [ Option "h?" ["help"]
+        ( NoArg $
+          \ x -> x { ao_help     = True }
+        )
+        "usage info"
+
+      , Option "" ["fct-index"]
+        ( NoArg $
+          \ x -> x { ao_pkgIndex = False
+                   , ao_crawlSfn = "./tmp/ix-"
+                   }
+        )
+        "process index for haddock functions and types (default)"
+
+      , Option "" ["pkg-index"]
+        ( NoArg $
+          \ x -> x { ao_pkgIndex = True
+                   , ao_crawlSfn = "./tmp/pkg-"
+                   }
+        )
+        "process index for hackage package description pages"
+
+      , Option "" ["cache"]
+        ( NoArg $
+          \ x -> x { ao_action   = BuildCache }
+        )
+        "update the cache"
+                   
+      , Option "i" ["index"]
+        ( ReqArg
+          (\ f x -> x { ao_index = f })
+          "INDEX"
+        )
+        "index input file (binary format) to be operated on"
+
+      , Option "n" ["new-index"]
+        ( ReqArg
+          (\ f x -> x { ao_ixout = f })
+          "NEW-INDEX"
+        )
+        "new index file (binary format) to be generatet, default is index file"
+
+      , Option "s" ["new-search"]
+        ( ReqArg
+          (\ f x -> x { ao_ixsearch = f })
+          "SEARCH-INDEX"
+        )
+        "new search index files (binary format) ready to be used by Hayoo! search"
+
+      , Option "x" ["xml-output"]
+        ( ReqArg
+          (\ f x -> x { ao_xml = f })
+          "XML-FILE"
+        )
+        "output of final crawler state in xml format, \"-\" for stdout"
+
+      , Option "r" ["resume"]
+        ( ReqArg (\ s x -> x { ao_resume = Just s})
+          "FILE"
+        )
+        "resume program with file containing saved intermediate state"
+                   
+      , Option "p" ["packages"]
+        ( ReqArg
+          (\ l x -> x { ao_packages = pkgList l })
+          "PACKAGE-LIST"
+        )
+        "packages to be processed, a comma separated list of package names"
+
+      , Option "u" ["update"]
+        ( NoArg $
+          \ x -> x { ao_action   = UpdatePkg }
+        )
+        "update packages specified by \"packages\" option"
+
+      , Option "d" ["delete"]
+        ( NoArg $
+          \ x -> x { ao_action   = RemovePkg }
+        )
+        "delete packages specified by \"packages\" option"
+
+      , Option "" ["maxdocs"]
+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlDoc = setMaxDocs i $
+                                                                 ao_crawlDoc x }))
+          "NUMBER"
+        )
+        "maximum # of docs to be processed"
+
+      , Option "" ["maxthreads"]
+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlDoc = setMaxThreads i $
+                                                                 ao_crawlDoc x }))
+          "NUMBER"
+        )
+        ( "maximum # of parallel threads, 0: sequential, 1: single thread with binary merge," ++
+          " else real parallel threads, default: 1" )
+
+      , Option "" ["maxpar"]
+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlDoc = setMaxParDocs i $
+                                                                 ao_crawlDoc x }))
+          "NUMBER"
+        )
+        "maximum # of docs indexed at once before the results are inserted into index, default: 1024"
+
+      , Option "" ["valid"]
+        ( ReqArg (setOption parseTime (\ x t -> x { ao_crawlPar = setDocAge t $
+                                                                  ao_crawlPar x }))
+          "DURATION"
+        )
+        ( "validate cache for pages older than given time, format: " ++
+          "10sec, 5min, 20hours, 3days, 5weeks, 1month, default is 1month" )
+
+      , Option "" ["latest"]
+        ( ReqArg (setOption parseTime (\ x t -> x { ao_latest   = Just t }))
+          "DURATION"
+        )
+        "select latest packages newer than given time, format like in option \"valid\""
+
+      , Option "" ["partition"]
+        ( ReqArg (setOption parseInt (\ x i -> x { ao_partix    = True
+                                                 , ao_crawlSav  = i }))
+          "NUMBER"
+        )
+        "partition the index into smaller chunks of given # of docs and write the index part by part"
+
+      , Option "" ["merge"]
+        ( ReqArg (\ s x -> x { ao_action = MergeIx
+                             , ao_resume = Just s })
+          "FILE"
+        )
+        "merge chunks into final index, resume with latest crawler state"
+
+      , Option "" ["save"]
+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlSav  = i }))
+          "NUMBER"
+        )
+        "save intermediate results of index, default is 5000"
+
+      , Option "" ["defragment"]
+        ( NoArg $
+          \ x -> x { ao_defrag    = True }
+        )
+        "defragment index after delete or update"
+
+      , Option "" ["hackage"]
+        ( NoArg $
+          \ x -> x { ao_getHack   = True }
+        )
+        "when processing latest packages, first update the package list from hackage"
+
+      , Option "" ["ranking"]
+        ( NoArg $
+          \   x -> x { ao_pkgRank   = True }
+        )
+        "when processing package index, compute package rank, default is no rank"
+                   
+      ]
+    where
+    pkgList
+        = words . map (\ x -> if x == ',' then ' ' else x)
+
+    setOption parse f s x
+        = either (\ e -> x { ao_msg  = e
+                           , ao_help = True
+                           }
+                 ) (f x) . parse $ s
+
+-- ------------------------------------------------------------
+
+parseInt                                :: String -> Either String Int
+parseInt s
+    | match "[0-9]+" s                  = Right $ read s
+    | otherwise                         = Left  $ "number expected in option arg"
+
+parseTime                               :: String -> Either String Int
+parseTime s
+    | match "[0-9]+(s(ec)?)?"      s    = Right $ t
+    | match "[0-9]+(m(in)?)?"      s    = Right $ t * 60
+    | match "[0-9]+(h(our(s)?)?)?" s    = Right $ t * 60 * 60
+    | match "[0-9]+(d(ay(s)?)?)?"  s    = Right $ t * 60 * 60 * 24
+    | match "[0-9]+(w(eek(s)?)?)?" s    = Right $ t * 60 * 60 * 24 * 7
+    | match "[0-9]+(m(onth(s)?)?)?" s   = Right $ t * 60 * 60 * 24 * 30
+    | match "[0-9]+(y(ear(s)?)?)?" s    = Right $ t * 60 * 60 * 24 * 30 * 365
+    | otherwise                         = Left  $ "error in duration format in option arg"
+    where
+    t                                   = read . filter isDigit $ s
+
+-- ------------------------------------------------------------
+
+setMaxDocs                              :: Int -> (Int, Int, Int) -> (Int, Int, Int)
+setMaxDocs    md (_md, mp, mt)          = (md, md `min` mp, mt)
+
+setMaxParDocs                           :: Int -> (Int, Int, Int) -> (Int, Int, Int)
+setMaxParDocs mp (md, _mp, mt)          = (md, mp, mt)
+
+setMaxThreads                           :: Int -> (Int, Int, Int) -> (Int, Int, Int)
+setMaxThreads mt (md, mp, _mt)          = (md, mp, mt)
+
+setDocAge                               :: Int -> SysConfig -> SysConfig
+setDocAge d                             = (>>> withCache' d)
+
+-- ------------------------------------------------------------
diff --git a/src/HayooSearch.hs b/src/HayooSearch.hs
new file mode 100644
--- /dev/null
+++ b/src/HayooSearch.hs
@@ -0,0 +1,37 @@
+  -- ----------------------------------------------------------------------------
+
+{- |
+  Module     : Main
+  Copyright  : Copyright (C) 2011 Timo B. Huebel
+  License    : MIT
+
+  Maintainer : Timo B. Huebel (tbh@holumbus.org)
+  Stability  : experimental
+  Portability: portable
+  Version    : 0.1
+
+  Main program to use as standalone webserver for serving Hayoo!
+
+-}
+
+-- ----------------------------------------------------------------------------
+
+module Main where
+
+import Hayoo.Search.Application
+
+import Hack.Handler.SimpleServer
+
+-- ----------------------------------------------------------------------------
+
+-- | Maybe read these from the command line ... somewhen
+ixBase :: FilePath
+ixBase          = "./lib"
+
+-- | The main application, fire up the server here!
+main :: IO ()
+main = do
+       apl <- hayooInit ixBase
+       run 4242 $ apl
+
+-- ----------------------------------------------------------------------------
diff --git a/src/HayooSnap.hs b/src/HayooSnap.hs
new file mode 100644
--- /dev/null
+++ b/src/HayooSnap.hs
@@ -0,0 +1,65 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+{-|
+
+This is the entry point for this web server application.  It supports
+easily switching between interpreting source and running statically
+compiled code.
+
+In either mode, the generated program should be run from the root of
+the project tree.  When it is run, it locates its templates, static
+content, and source files in development mode, relative to the current
+working directory.
+
+When compiled with the development flag, only changes to the
+libraries, your cabal file, or this file should require a recompile to
+be picked up.  Everything else is interpreted at runtime.  There are a
+few consequences of this.
+
+First, this is much slower.  Running the interpreter takes a
+significant chunk of time (a couple tenths of a second on the author's
+machine, at this time), regardless of the simplicity of the loaded
+code.  In order to recompile and re-load server state as infrequently
+as possible, the source directories are watched for updates, as are
+any extra directories specified below.
+
+Second, the generated server binary is MUCH larger, since it links in
+the GHC API (via the hint library).
+
+Third, and the reason you would ever want to actually compile with
+development mode, is that it enables a faster development cycle. You
+can simply edit a file, save your changes, and hit reload to see your
+changes reflected immediately.
+
+When this is compiled without the development flag, all the actions
+are statically compiled in.  This results in faster execution, a
+smaller binary size, and having to recompile the server for any code
+change.
+
+-}
+
+module Main where
+
+#ifdef DEVELOPMENT
+import           Snap.Extension.Loader.Devel
+import           Snap.Http.Server (quickHttpServe)
+#else
+import           Snap.Extension.Server
+#endif
+
+import           Hayoo.Snap.Application
+import           Hayoo.Snap.Site
+
+main :: IO ()
+#ifdef DEVELOPMENT
+main = do
+    -- All source directories will be watched for updates
+    -- automatically.  If any extra directories should be watched for
+    -- updates, include them here.
+    snap <- $(let extraWatcheDirs = ["resources/templates"]
+              in loadSnapTH 'applicationInitializer 'site extraWatcheDirs)
+    quickHttpServe snap
+#else
+main = quickHttpServe applicationInitializer site
+#endif
