Gerrit plug-in branch-network HTML5 Canvas.

First implementation of the HTML5 Canvas plug-in
for Gerrit to display the Branch-network graphically.

This plugin can be used in two forms:
a) GitWeb link to Project details: link to commits
   will point then to the GitWeb details using the
   configured GitWeb provider (GitBlit, GitWeb or others).
b) HTML5 pure canvas to be used in other Gerrit UI
   plugins.

Change-Id: I5ca51259b467a7cae220517a1da8c11d2f57a91b
Signed-off-by: Luca Milanesio <luca@milanesio.org>
Signed-off-by: Luca Milanesio <luca.milanesio@gmail.com>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8bd3a05
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target/
+/.settings/
+/.classpath
+/.project
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..ba6fd24
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<!--
+Copyright (C) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>branch-network</artifactId>
+  <groupId>com.googlesource.gerrit.plugins.branchnetwork</groupId>
+  <version>1.0-SNAPSHOT</version>
+  <name>Gerrit - Branch network display plugin</name>
+  <url>http://maven.apache.org</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <Gerrit-ApiType>plugin</Gerrit-ApiType>
+    <Gerrit-ApiVersion>2.6-SNAPSHOT</Gerrit-ApiVersion>
+    <Gerrit-ReloadMode>reload</Gerrit-ReloadMode>
+    <Gerrit-Module>com.googlesource.gerrit.plugins.branchnetwork.GitCommitCacheModule</Gerrit-Module>
+    <Gerrit-HttpModule>com.googlesource.gerrit.plugins.branchnetwork.NetworkGraphModule</Gerrit-HttpModule>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>6.0.29</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-server</artifactId>
+      <version>${Gerrit-ApiVersion}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gerrit</groupId>
+      <artifactId>gerrit-httpd</artifactId>
+      <version>${Gerrit-ApiVersion}</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.5.1</version>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.4</version>
+        <configuration>
+          <archive>
+            <manifestEntries>
+              <Gerrit-Module>${Gerrit-Module}</Gerrit-Module>
+              <Gerrit-HttpModule>${Gerrit-HttpModule}</Gerrit-HttpModule>
+              <Implementation-Vendor>GerritForge LLP</Implementation-Vendor>
+              <Implementation-URL>http://gerritforge.com</Implementation-URL>
+              <Implementation-Title>Plugin ${project.artifactId}</Implementation-Title>
+              <Implementation-Version>${project.version}</Implementation-Version>
+              <Gerrit-ApiType>${Gerrit-ApiType}</Gerrit-ApiType>
+              <Gerrit-ApiVersion>${Gerrit-ApiVersion}</Gerrit-ApiVersion>
+              <Gerrit-ReloadMode>${Gerrit-ReloadMode}</Gerrit-ReloadMode>
+            </manifestEntries>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/GitCommitCacheModule.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/GitCommitCacheModule.java
new file mode 100644
index 0000000..d54a17b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/GitCommitCacheModule.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork;
+
+import java.util.List;
+
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.inject.TypeLiteral;
+import com.googlesource.gerrit.plugins.branchnetwork.data.GitCommitCache;
+import com.googlesource.gerrit.plugins.branchnetwork.data.GitCommitCacheRefresh;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Commit;
+
+public class GitCommitCacheModule extends CacheModule {
+
+  @Override
+  protected void configure() {
+    cache(GitCommitCache.GRAPH_DATA_CACHE, String.class,
+        new TypeLiteral<List<Commit>>() {}).loader(GitCommitCache.class);
+
+    DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(
+        GitCommitCacheRefresh.class);
+    DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(
+        GitCommitCacheRefresh.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/NetworkGraphModule.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/NetworkGraphModule.java
new file mode 100644
index 0000000..54453ae
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/NetworkGraphModule.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork;
+
+import com.google.inject.servlet.ServletModule;
+import com.googlesource.gerrit.plugins.branchnetwork.canvas.GitGraphServlet;
+import com.googlesource.gerrit.plugins.branchnetwork.canvas.NetworkDataChunkService;
+import com.googlesource.gerrit.plugins.branchnetwork.canvas.NetworkMetaService;
+
+public class NetworkGraphModule extends ServletModule {
+  @Override
+  protected void configureServlets() {
+    serve("/").with(GitGraphServlet.class);
+    serve("/network_meta/*").with(NetworkMetaService.class);
+    serve("/network_data_chunk/*").with(NetworkDataChunkService.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/GitGraphServlet.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/GitGraphServlet.java
new file mode 100644
index 0000000..52b34ad
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/GitGraphServlet.java
@@ -0,0 +1,194 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.canvas;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.httpd.GitWebConfig;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectControl.Factory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GitGraphServlet extends HttpServlet {
+
+  private static final long serialVersionUID = -822406714154950673L;
+  private static final int PROGRESS_BAR_WIDTH = 208;
+  private static final int PROGRESS_BAR_HEIGHT = 13;
+  private String canonicalPath;
+  private Factory projectControl;
+  private GitRepositoryManager repoManager;
+  private GitWebConfig gitWebConfig;
+
+  @Inject
+  public GitGraphServlet(@PluginName String pluginName,
+      @GerritServerConfig final Config gerritConfig, GitWebConfig gitWebConfig,
+      final ProjectControl.Factory projectControl,
+      final GitRepositoryManager repoManager)
+      throws MalformedURLException {
+    URL url =
+        new URL(gerritConfig.getString("gerrit", null, "canonicalWebUrl"));
+    String pathPrefix = "/";
+    if(url != null && url.getPath() != null) {
+      pathPrefix = url.getPath();
+      if(!pathPrefix.endsWith("/")) {
+        pathPrefix += "/";
+      }
+    }
+    this.canonicalPath =
+        String.format("%splugins/%s/", pathPrefix, pluginName);
+    this.projectControl = projectControl;
+    this.repoManager = repoManager;
+    this.gitWebConfig = gitWebConfig;
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+      throws ServletException, IOException {
+    PrintWriter out = resp.getWriter();
+
+    try {
+      String repoName = req.getParameter("p");
+      if (repoName == null) {
+        resp.sendRedirect("Documentation/index.html");
+        return;
+      }
+
+      boolean naked = req.getParameter("naked") != null;
+      int width = getParam(req, "width", 920);
+      int height = getParam(req, "height", 600);
+
+      if (repoName.endsWith(".git")) {
+        repoName = repoName.substring(0, repoName.length() - 4);
+      }
+
+      final Project.NameKey nameKey = new Project.NameKey(repoName);
+      try {
+        projectControl.validateFor(nameKey);
+      } catch (NoSuchProjectException e) {
+        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+        return;
+      }
+
+      Repository repo = null;
+      try {
+        repo = repoManager.openRepository(nameKey);
+      } catch (RepositoryNotFoundException e) {
+        getServletContext().log("Cannot open repository", e);
+        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return;
+      } finally {
+        if (repo != null) {
+          repo.close();
+        }
+      }
+
+      String networkMetaUrl =
+          String.format("%1$snetwork_meta/%2$s/", canonicalPath, repoName);
+      String networkDataChunkUrl =
+          String.format("%1$snetwork_data_chunk/%2$s/?nethash=", canonicalPath,
+              repoName);
+
+      String commitUrlPattern =
+          gitWebConfig.getUrl()
+              + gitWebConfig.getGitWebType().getRevision()
+                  .replaceAll("\\$\\{project\\}", repoName)
+                  .replaceAll("\\$\\{commit\\}", "{2}");
+      String projectPattern =
+          gitWebConfig.getUrl()
+              + gitWebConfig.getGitWebType().getProject()
+                  .replaceAll("\\$\\{project\\}", repoName);
+
+      commitUrlPattern =
+          (commitUrlPattern.startsWith("/") ? commitUrlPattern : "/"
+              + commitUrlPattern);
+      projectPattern =
+          (projectPattern.startsWith("/") ? projectPattern : "/"
+              + projectPattern);
+
+      String header =
+          "<html>\n" + "<head>\n" + "<style type=\"text/css\">\n"
+              + "div#progress\n" + "{\n" + "position:absolute;\n" + "left:"
+              + (width - PROGRESS_BAR_WIDTH) / 2
+              + "px;\n"
+              + "top:"
+              + (height - PROGRESS_BAR_HEIGHT) / 2
+              + "px;\n"
+              + "z-index:-1;\n"
+              + "}\n"
+              + "div#graph\n"
+              + "{\n"
+              + "position:absolute;\n"
+              + "left:0px;\n"
+              + "top:0px;\n"
+              + "z-index:0;\n"
+              + "}\n"
+              + "</style>\n"
+              + "</head>\n"
+              + "<body>\n"
+              + "<div id=\"progress\" >"
+              + "<img src=\""
+              + canonicalPath
+              + "static/progress_bar.gif\" />"
+              + "</div>"
+              + "<div id=\"graph\">";
+      String javaScript =
+          "<script type=\"text/javascript\" src=\"" + canonicalPath
+              + "static/jquery-1.4.2.min.js\"></script>\n"
+              + "<script type=\"text/javascript\" src=\"" + canonicalPath
+              + "static/network.js\"></script>\n"
+              + "<script type=\"text/javascript\">"
+              + "$(document).ready( function() {\n"
+              + "new NetworkCanvas('network-canvas', " + width + ", " + height
+              + " , null, null, null,\n" + "'" + commitUrlPattern + "',\n"
+              + "'" + projectPattern + "',\n" + "'" + networkMetaUrl + "',\n"
+              + "'" + networkDataChunkUrl + "');\n" + "});\n" + "</script>\n";
+      String canvas =
+          "<canvas id=\"network-canvas\" width=\"" + width + "\" height=\""
+              + height + "\" style=\"cursor: default; \"></canvas>";
+      String footer = "</div>" + "</body>\n" + "</html>\n";
+      out.println(naked ? (javaScript + canvas)
+          : (header + javaScript + canvas + footer));
+    } finally {
+      out.close();
+    }
+  }
+
+  private int getParam(HttpServletRequest req, String name, int defValue) {
+    String value = req.getParameter(name);
+    if (value != null) {
+      return Integer.parseInt(value);
+    } else {
+      return defValue;
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/JsonService.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/JsonService.java
new file mode 100644
index 0000000..282e12e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/JsonService.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.canvas;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Type;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.stream.JsonWriter;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Parent;
+
+public class JsonService extends HttpServlet {
+  private static final long serialVersionUID = -909762366548464514L;
+  private static final Gson gson = getGson();
+
+  private static Gson getGson() {
+    GsonBuilder gsonBuilder = new GsonBuilder();
+    gsonBuilder.registerTypeAdapter(Parent.class, new JsonSerializer<Parent>() {
+      @Override
+      public JsonElement serialize(Parent src, Type typeOfSrc,
+          JsonSerializationContext context) {
+        return src.toJson();
+      }
+    });
+    return gsonBuilder.create();
+  }
+
+  protected void returnJsonResponse(HttpServletResponse resp, Object data)
+      throws UnsupportedEncodingException, IOException {
+    JsonWriter writer =
+        new JsonWriter(new OutputStreamWriter(resp.getOutputStream(), "UTF-8"));
+    try {
+      gson.toJson(data, data.getClass(), writer);
+    } finally {
+      writer.close();
+    }
+  }
+
+  protected String getProjectName(HttpServletRequest req) {
+    String reqURI = req.getRequestURI();
+    String[] reqParts = reqURI.split("/");
+    return reqParts[reqParts.length-1];
+  }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/NetworkDataChunkService.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/NetworkDataChunkService.java
new file mode 100644
index 0000000..f4e705d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/NetworkDataChunkService.java
@@ -0,0 +1,78 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.canvas;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.common.cache.LoadingCache;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.googlesource.gerrit.plugins.branchnetwork.data.GitCommitCache;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Commit;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Data;
+
+@Singleton
+public class NetworkDataChunkService extends JsonService {
+
+  private static final long serialVersionUID = -920473696617437261L;
+  private static final int MAX_COMMIT_CHUNK_LEN = 100;
+  public static String PATH_SUFFIX = "network_data_chunk";
+  private final LoadingCache<String, List<Commit>> networkGraphDataCache;
+
+  @Inject
+  public NetworkDataChunkService(
+      @Named(GitCommitCache.GRAPH_DATA_CACHE) final LoadingCache<String, List<Commit>> networkGraphDataCache) {
+    this.networkGraphDataCache = networkGraphDataCache;
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+      throws ServletException, IOException {
+    try {
+      printJson(req, resp, getProjectName(req));
+    } catch (ExecutionException e) {
+      throw new ServletException(e);
+    }
+  }
+
+  public void printJson(HttpServletRequest req, HttpServletResponse resp,
+      String project) throws IOException, ExecutionException {
+
+    String domainRepoName = req.getParameter("nethash");
+
+    List<Commit> commits = networkGraphDataCache.get(domainRepoName);
+    int endInt = commits.size() - 1;
+    int startInt = 0;
+
+    String start = req.getParameter("start");
+    if (start != null) startInt = Integer.parseInt(start);
+
+    String end = req.getParameter("end");
+    if (end != null) endInt = Integer.parseInt(end);
+
+    if ((endInt - startInt) > MAX_COMMIT_CHUNK_LEN)
+      startInt = endInt - MAX_COMMIT_CHUNK_LEN;
+
+    Data data = new Data(commits.subList(startInt, endInt + 1));
+    returnJsonResponse(resp, data);
+  }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/NetworkMetaService.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/NetworkMetaService.java
new file mode 100644
index 0000000..374245e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/canvas/NetworkMetaService.java
@@ -0,0 +1,110 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.canvas;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.branchnetwork.data.GitCommitCache;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Head;
+
+@Singleton
+public class NetworkMetaService extends JsonService {
+  public static String PATH_SUFFIX = "network_meta";
+  private static final long serialVersionUID = 1L;
+  private static final int COMMITS_FIRST_IN_FOCUS = 30;
+
+  private final GitCommitCache gitCommitCache;
+
+  @Inject
+  public NetworkMetaService(final GitCommitCache gitCommitCache) {
+    this.gitCommitCache = gitCommitCache;
+  }
+
+  /**
+   * Beans intended to be used for JSON serialisation
+   */
+  public static class MetaData {
+    public List<Block> blocks = new LinkedList<Block>();
+    public List<String> dates = new LinkedList<String>();
+    public List<User> users = new ArrayList<User>();
+    public int focus;
+    public String nethash;
+  }
+
+  public static class User {
+    public String repo;
+    public String name;
+    public List<Head> heads = new ArrayList<Head>();
+
+    public User(String project, List<Head> heads) {
+      this.repo = project;
+      this.name = project;
+      this.heads = heads;
+    }
+  }
+
+  public static class Block {
+    public String name;
+    public int count;
+    public int start;
+
+    public Block(String name, int count, int start) {
+      this.name = name;
+      this.count = count;
+      this.start = start;
+    }
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+      throws ServletException, IOException {
+    returnRepositoryGraphMetaDataAsJson(req, resp, getProjectName(req));
+  }
+
+  public void returnRepositoryGraphMetaDataAsJson(HttpServletRequest req,
+      HttpServletResponse resp, String project) throws IOException {
+
+    MetaData metaData = new MetaData();
+
+    int laneCount = 0;
+
+    String nethash = project;
+
+    List<String> dates = gitCommitCache.getDates(project);
+
+    int maxTime = dates.size();
+
+    List<Head> heads = gitCommitCache.getHeads(project);
+    metaData.focus =
+        maxTime < COMMITS_FIRST_IN_FOCUS ? 0 : maxTime - COMMITS_FIRST_IN_FOCUS;
+
+    laneCount = gitCommitCache.getBranchesPlotLanesCount(project);
+    metaData.blocks.add(new Block(project, laneCount, 0));
+    metaData.users.add(new User(project, heads));
+    metaData.dates = dates;
+    metaData.nethash = nethash;
+
+    returnJsonResponse(resp, metaData);
+  }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/GitCommitCache.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/GitCommitCache.java
new file mode 100644
index 0000000..a8b81b2
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/GitCommitCache.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.List;
+
+import com.google.common.cache.CacheLoader;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Commit;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Head;
+
+@Singleton
+public class GitCommitCache extends CacheLoader<String, List<Commit>> {
+  public static final String GRAPH_DATA_CACHE = "NetworkGraphDataCache";
+
+  @Inject
+  private JGitFacade git;
+
+  public List<Head> getHeads(String project) throws IOException {
+    return git.getHeadsForRepository(project);
+  }
+
+  @Override
+  public List<Commit> load(String project) throws IOException, ParseException {
+    return git.logData(project);
+  }
+
+  public List<String> getDates(String project) throws IOException {
+    return git.getDatesForRepository(project);
+  }
+
+  public int getBranchesPlotLanesCount(String project) throws IOException {
+    return git.getBranchesPlotLanesCount(project);
+  }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/GitCommitCacheRefresh.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/GitCommitCacheRefresh.java
new file mode 100644
index 0000000..6675e13
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/GitCommitCacheRefresh.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data;
+
+import java.util.List;
+
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Commit;
+
+public class GitCommitCacheRefresh implements GitReferenceUpdatedListener,
+    NewProjectCreatedListener {
+  private LoadingCache<String, List<Commit>> networkGraphDataCache;
+  private final LocalDiskRepositoryManager repoManager;
+
+  @Inject
+  public GitCommitCacheRefresh(
+      @Named(GitCommitCache.GRAPH_DATA_CACHE) final LoadingCache<String, List<Commit>> networkGraphDataCache,
+      final LocalDiskRepositoryManager repoManager) {
+    this.networkGraphDataCache = networkGraphDataCache;
+    this.repoManager = repoManager;
+  }
+
+  @Override
+  public void onNewProjectCreated(
+      com.google.gerrit.extensions.events.NewProjectCreatedListener.Event event) {
+    networkGraphDataCache.refresh(event.getProjectName());
+    repoManager.list(); // Invoked for flushing the LocalDiskRepositoryManager project list cache
+
+  }
+
+  @Override
+  public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
+    for (Update update : event.getUpdates()) {
+      if (update.getRefName().startsWith("refs/heads")) {
+        networkGraphDataCache.refresh(event.getProjectName());
+        return;
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/JGitFacade.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/JGitFacade.java
new file mode 100644
index 0000000..f0f0ab8
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/JGitFacade.java
@@ -0,0 +1,196 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revplot.PlotCommit;
+import org.eclipse.jgit.revplot.PlotCommitList;
+import org.eclipse.jgit.revplot.PlotLane;
+import org.eclipse.jgit.revplot.PlotWalk;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevCommitList;
+import org.eclipse.jgit.revwalk.RevSort;
+
+import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Commit;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Head;
+import com.googlesource.gerrit.plugins.branchnetwork.data.json.Parent;
+
+@Singleton
+public class JGitFacade {
+  private final GitRepositoryManager repoManager;
+
+  @Inject
+  public JGitFacade(GitRepositoryManager repoManager) {
+    this.repoManager = repoManager;
+  }
+
+  public List<Commit> logData(String repository) throws IOException {
+    final Repository repo =
+        repoManager.openRepository(NameKey.parse(repository));
+    final PlotWalk walk = new PlotWalk(repo);
+    try {
+
+      List<Head> heads = getHeadsForRepository(repo);
+      for (Head head : heads) {
+        ObjectId headId = repo.resolve(head.getId());
+        walk.markStart(walk.parseCommit(headId));
+      }
+
+      walk.sort(RevSort.COMMIT_TIME_DESC, true);
+
+      PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+      pcl.source(walk);
+      pcl.fillTo(Integer.MAX_VALUE);
+      Collections.reverse(pcl);
+
+      List<Commit> commits = new LinkedList<Commit>();
+      final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+      Map<String, Commit> commitsById = new HashMap<String, Commit>(pcl.size());
+      for (int i = 0; i < pcl.size(); i++) {
+        PlotCommit<PlotLane> pc = pcl.get(i);
+        Commit commit = new Commit();
+
+        commit.setAuthor(pc.getAuthorIdent().getName());
+        commit.setDate(sdf.format(pc.getAuthorIdent().getWhen()));
+        commit.setId(pc.getId().getName());
+        commit.setEmail(pc.getAuthorIdent().getEmailAddress());
+        commit.setMessage(pc.getFullMessage());
+        if (pc.getLane() != null) {
+          commit.setSpace(1 + pc.getLane().getPosition());
+        } else {
+          commit.setSpace(1);
+        }
+        commit.setTime(i);
+
+        for (RevCommit parentRC : pc.getParents()) {
+          Commit parentCommit = commitsById.get(parentRC.getId().getName());
+          assert parentCommit != null;
+          Parent parent = new Parent();
+          parent.setId(parentCommit.getId());
+          parent.setTime(parentCommit.getTime());
+          parent.setSpace(parentCommit.getSpace());
+          commit.addParent(parent);
+        }
+
+        commitsById.put(commit.getId(), commit);
+        commits.add(commit);
+      }
+
+      return commits;
+    } finally {
+      walk.dispose();
+      repo.close();
+    }
+  }
+
+  public List<String> getDatesForRepository(String repoName) throws IOException {
+    List<String> dates = new LinkedList<String>();
+
+    Repository repo = repoManager.openRepository(NameKey.parse(repoName));
+    final PlotWalk walk = new PlotWalk(repo);
+
+    try {
+      List<Head> heads = getHeadsForRepository(repo);
+      for (Head head : heads) {
+        ObjectId headId = repo.resolve(head.getId());
+        walk.markStart(walk.parseCommit(headId));
+      }
+
+      walk.sort(RevSort.COMMIT_TIME_DESC, true);
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+      RevCommitList<RevCommit> rcl = new RevCommitList<RevCommit>();
+      rcl.source(walk);
+      rcl.fillTo(Integer.MAX_VALUE);
+
+      for (RevCommit rc : rcl) {
+        dates.add(sdf.format(rc.getCommitterIdent().getWhen()));
+      }
+      Collections.reverse(dates);
+
+      return dates;
+    } finally {
+      repo.close();
+      walk.dispose();
+    }
+  }
+
+  public List<Head> getHeadsForRepository(String repoName) throws IOException {
+    Repository repo = repoManager.openRepository(NameKey.parse(repoName));
+    try {
+      return getHeadsForRepository(repo);
+    } finally {
+      repo.close();
+    }
+  }
+
+  private List<Head> getHeadsForRepository(Repository repo) throws IOException {
+    Map<String, Ref> headRefs =
+        repo.getRefDatabase().getRefs(Constants.R_HEADS);
+
+    List<Head> heads = new LinkedList<Head>();
+    for (String headName : headRefs.keySet()) {
+      Head head = new Head();
+      head.setName(headName);
+      head.setId(headRefs.get(headName).getObjectId().getName());
+      heads.add(head);
+    }
+
+    return heads;
+  }
+
+  public int getBranchesPlotLanesCount(String repoName) throws IOException {
+    final Repository repo =
+        repoManager.openRepository(NameKey.parse(repoName));
+    final PlotWalk walk = new PlotWalk(repo);
+
+    try {
+      ObjectId headId = repo.resolve(Constants.HEAD);
+      if (headId == null) return 0;
+
+      walk.markStart(walk.parseCommit(headId));
+      PlotCommitList<PlotLane> pcl = new PlotCommitList<PlotLane>();
+      pcl.source(walk);
+      pcl.fillTo(Integer.MAX_VALUE);
+
+      int maxLane = 1;
+      for (PlotCommit<PlotLane> pc : pcl) {
+        if (pc.getLane() != null) {
+          int lane = 1 + pc.getLane().getPosition();
+          if (lane > maxLane) maxLane = lane;
+        }
+      }
+
+      return maxLane;
+    } finally {
+      walk.dispose();
+      repo.close();
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Commit.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Commit.java
new file mode 100644
index 0000000..36abd80
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Commit.java
@@ -0,0 +1,124 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data.json;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Commit  {
+
+    private String author;
+    private List<Parent> parents = new ArrayList<Parent>();
+    private int time;
+    private String date;
+    private String id;
+    private int space;
+    private String email;
+    private String message;
+    private String gravatar;
+
+    public String generateGravatar(String email) {
+          if (email == null) {
+              return null;
+          }
+
+          try {
+              MessageDigest m = MessageDigest.getInstance("MD5");
+              m.update(email.toLowerCase().getBytes());
+              BigInteger i = new BigInteger(1, m.digest());
+              return String.format("%1$032x", i);
+          } catch (NoSuchAlgorithmException e) {
+            return null;
+          }
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public List<Parent> getParents() {
+        return parents;
+    }
+
+    public int getTime() {
+        return time;
+    }
+
+    public String getDate() {
+        return date;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public int getSpace() {
+        return space;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void addParent(Parent parent) {
+        this.parents.add(parent);
+    }
+
+
+    public void setAuthor(String author) {
+        this.author = author;
+    }
+
+    public void setParents(List<Parent> parents) {
+        this.parents = parents;
+    }
+
+    public void setTime(int time) {
+        this.time = time;
+    }
+
+    public void setDate(String date) {
+        this.date = date;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setSpace(int space) {
+        this.space = space;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+        this.gravatar = generateGravatar(email);
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+    return id + " (" + date + ")";
+    }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Data.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Data.java
new file mode 100644
index 0000000..9185372
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Data.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data.json;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Data {
+  List<Commit> commits = new ArrayList<Commit>();
+
+  public Data(List<Commit> commits) {
+    this.commits = commits;
+  }
+
+  public List<Commit> getCommits() {
+    return commits;
+  }
+
+  public void addCommit(Commit commit) {
+    commits.add(commit);
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Head.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Head.java
new file mode 100644
index 0000000..4b69845
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Head.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data.json;
+
+public class Head  {
+
+  private String name;
+  private String id;
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Parent.java b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Parent.java
new file mode 100644
index 0000000..7a3e59d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/branchnetwork/data/json/Parent.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.googlesource.gerrit.plugins.branchnetwork.data.json;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+
+public class Parent {
+
+    private String id;
+
+    private int time;
+
+    private int space;
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setTime(int time) {
+        this.time = time;
+    }
+
+    public int getTime() {
+        return time;
+    }
+
+    public void setSpace(int space) {
+        this.space = space;
+    }
+
+    public int getSpace() {
+        return space;
+    }
+
+    public JsonElement toJson() {
+      JsonArray jsonOut = new JsonArray();
+      jsonOut.add(new JsonPrimitive(id));
+      jsonOut.add(new JsonPrimitive(time));
+      jsonOut.add(new JsonPrimitive(space));
+      return jsonOut;
+    }
+
+
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..9adfc69
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,55 @@
+Git branch network graph
+=========================
+
+This plugin allows the rendering of Git repository branch network
+in a graphical HTML5 Canvas. It is mainly intended to be used as
+"project link" in a GitWeb configuration or by other Gerrit GWT UI
+plugins to be plugged elsewhere in Gerrit.
+
+GitWeb configuration
+--------------------
+
+In order to use branch-network plugin as GitWeb project viewer replacement
+simply add the following line to your existing Gerrit config under the
+GitWeb section (assuming plugin was copied as branch-network.jar):
+
+```
+  type = custom
+  url = plugins/
+  project = branch-network/?p=${project}
+```
+
+Usage in other GWT or JavaScript UI
+-----------------------------------
+
+The branch network canvas can be returned as HTML fragment to allow other
+GWT UI plugins the rendering the network graph in other ways.
+(i.e. adding an extra link on the Project details page and display the
+canvas on the right side panel)
+
+The syntax of branch-network plugin URL is similar to the one used in
+the GitWeb scenarios but with extra parameters to allow the "UX surgery"
+of the canvas in another UE.
+
+Additional parameters:
+
+naked=y
+:	allows to have the HTML5 Canvas and JavaScript without outer HTML page mark-up
+	element.
+
+width=N
+:	HTML5 Canvas width in pixels
+
+height=M
+:	HTML5 Canvas height in pixels
+
+Example
+-------
+
+The following URL allows to get a 1024x768 HTML5 Canvas in a naked HTML fragment.
+(assuming plugin was copied as branch-network.jar)
+
+```
+  branch-network/?p=${project}&naked=y&width=1024&height=768
+```
+
diff --git a/src/main/resources/static/jquery-1.4.2.min.js b/src/main/resources/static/jquery-1.4.2.min.js
new file mode 100644
index 0000000..7c24308
--- /dev/null
+++ b/src/main/resources/static/jquery-1.4.2.min.js
@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
diff --git a/src/main/resources/static/network.js b/src/main/resources/static/network.js
new file mode 100644
index 0000000..a93f502
--- /dev/null
+++ b/src/main/resources/static/network.js
@@ -0,0 +1,879 @@
+/* Copyright 2009-2010 Hugo Camboulive <hugo DOT camboulive AT gmail DOT com>
+ * Licensed under a BSD license */
+
+/* Note about canvas multi text line functions :
+ * It kinda sucks that the canvas does not provide its own
+ * functions. This function considers the input data's width
+ * is evenly distributed around characters. A sentence like
+ * "AAAAAAAA aaaaaaaa" split in two may pose problem. A rewrite
+ * will be necessary, but not a priority */
+/* This function returns the number one lines we need to use to
+ * write text over a length < maxwidth. It is only completely
+ * correct when using a monotype font but should be approximate
+ * enough for most uses. Use a margin just in case */
+CanvasRenderingContext2D.prototype.measureTextLines = function(str, maxwidth) {
+	var pixlen = this.measureText(str).width;
+	if (pixlen < maxwidth)
+		return 1;
+	return str.splitLine(parseInt(maxwidth * str.length / pixlen))[0];
+};
+
+/* This function draws text in a multiline way over over a
+ * length < maxwidth. It is only completely correct when
+ * using a monotype font but should be approximate enough
+ * for most uses. Use a margin just in case */
+CanvasRenderingContext2D.prototype.fillTextMultiLine = function(str, x, y, maxWidth, lineHeight) {
+	lineHeight = lineHeight || 12; /* default spacing = 12 */
+	var pixlen = this.measureText(str).width;
+	var strings = str.splitLine(parseInt(maxWidth * str.length / pixlen))[1];
+	var string_ar = strings.split("\n");
+	for(var i = 0 ; i < string_ar.length ; i++) {
+		y += lineHeight;
+		this.fillText(string_ar[i], x, y);
+	}
+};
+
+/* Line Splitter Function
+ * copyright Stephen Chapman, 19th April 2006
+ * you may copy this code but please keep the copyright notice as well */
+String.prototype.splitLine = function(n) {
+	var b = '';
+	var s = this;
+	var linecount = 1;
+	while (s.length > n) {
+		var c = s.substring(0,n);
+		var d = c.lastIndexOf(' ');
+		var e =c.lastIndexOf('\n');
+		if (e != -1)
+			d = e;
+		if (d == -1)
+			d = n;
+		b += c.substring(0,d) + '\n';
+		linecount += 1;
+		s = s.substring(d+1);
+	}
+	return [linecount, b+s];
+};
+
+/*
+ * http://stackoverflow.com/questions/1353408/messageformat-in-javascript-parameters-in-localized-ui-strings
+ */
+String.prototype.format = function() {
+    var args = arguments;
+
+    return this.replace(/\{(\d+)\}/g, function() {
+        return args[arguments[1]];
+    });
+};
+
+/* Constructor for a network canvas, we need to give it
+ * a canvas ID */
+NetworkCanvas = function(canvasid, width, height, names_width, meta, data, commitUrlTemplate, networkUrl, metaUrl, dataUrl) {
+	this.canvas = $('#'+canvasid).get(0);
+	this.height = height || 600;
+	this.width = width || 920;
+	this.canvas.width = this.width;
+	this.canvas.height = this.height;
+	this.commitUrlTemplate = commitUrlTemplate || "https://github.com/{0}/{1}/commit/{2}";
+	this.networkUrl = networkUrl || "/";
+	this.metaUrl = metaUrl || "network_meta";
+	this.dataUrl = dataUrl || "network_data_chunk?nethash=";
+
+	this.names_width = names_width || 100;
+	/* more stuff */
+	this.xoffset = this.names_width;/* the left column with names is 100 px wide */
+	this.yoffset = 40;		/* the two month/day bars at the top take 40px */
+
+	this.dotsMouseOver = [];	/* here we store the dots we can hover */
+	this.avatars = {};		/* we store images loaded from gravatars in there */
+
+	this.drawLabels = true;
+
+	this.maxx = - this.width + (this.xoffset * 2);
+	/* the +100 is just a margin in case we need to display HEADS */
+	this.maxy = - this.height + (this.yoffset * 2) + 100;
+	this.loading = false;
+	/* we iterate of those colors when drawing branches... later we'll include more */
+	this.branchColor = ["black", "red", "blue", "lawngreen", "magenta", "yellow", "orange", "cyan", "hotpink", "peru"];
+	this.usersBySpace = [];
+
+	/* Initialize mouse handler */
+	this.mouse = new NetworkCanvas.Mouse(this);
+	this.mouse.init();
+	/* Initialize keyboard handler */
+	this.keyboard = new NetworkCanvas.Keyboard(this);
+	this.keyboard.init();
+
+	/* Initialize the data loader */
+	this.dataManager = new NetworkCanvas.DataManager(this);
+	this.dataManager.init(meta, data);
+};
+
+NetworkCanvas.prototype = {
+	/* The main draw function, it load the context from the canvas
+	 * and draws everything on it. It's called everytime we have
+	 * to update the graphics. */
+	draw: function() {
+		/* if the data is not loaded yet, draw nothing */
+		if (!this.dataManager.meta)
+			return;
+		if (!this.dataManager.data)
+			return;
+
+		/* retreive the canvas */
+		if (this.canvas.getContext){
+			var ctx = this.canvas.getContext('2d');
+			ctx.font = "small sans-serif";
+			this.drawBlocks(ctx);
+			/* draw the data points and arrows */
+			this.drawData(ctx);
+			/* draw the names on the left */
+			this.drawNames(ctx);
+			/* draw the month bar */
+			this.drawMonthBar(ctx);
+			/* draw the day bar */
+			this.drawDayBar(ctx);
+			/* draw the months/days in the two bars */
+			this.drawDates(ctx);
+			/* hide the first 100px of the months/days bars */
+			ctx.fillStyle = "black";
+			ctx.fillRect(0,0,this.names_width,20);
+			ctx.fillStyle = "rgb(64,64,64)";
+			ctx.fillRect(0,20,this.names_width,20);
+			if (this.drawDot)
+				this.drawHint(ctx, this.drawDot);
+		}
+	},
+	/* Draw a 'hint' when a dot is mouse-hovered
+	 * It's basically a rectangle with smooth corners
+	 * a gravatar image + name + hash of the commit + comment */
+	drawHint: function(ctx, hint) {
+		var x = (this.names_width * 2) + (20 * hint.time) - this.xoffset + 10;
+		var y = 80 + 20 * hint.space - this.yoffset - 10;
+		/* compute the height of the hint box */
+		ctx.font = "small sans-serif";
+		ctx.fillStyle = "black";
+		var txtlen = ctx.measureTextLines(hint.message, 400 - 15 - 15);
+		var maxy = 70 + txtlen * 15 + 5;
+		/* reposition the hint horizontally */
+		if (x + 10 + 400 > this.width) {
+			x = this.width - 400 - 10;
+		} else if (x + 10 < this.names_width) {
+			x = this.names_width + 10;
+		} else {
+			x += 10;
+		}
+		/* reposition the hint vertically */
+		if (y + 10 + maxy > this.height) {
+			/* not enough space under the dot */
+			y = Math.max(40, y - 10 - maxy);
+		} else if (y < 40) {
+			y = 40 + 10;
+		} else {
+			y += 10;
+		}
+		/* draw the smoothed rectangle */
+		ctx.beginPath();
+		ctx.strokeStyle = "black";
+		ctx.fillStyle = "white";
+		ctx.lineWidth = "2";
+		ctx.moveTo(x,y+5);
+		ctx.quadraticCurveTo(x, y, x+5, y);
+		ctx.lineTo(x + 400 - 5, y);
+		ctx.quadraticCurveTo(x + 400, y, x + 400, y + 5);
+		ctx.lineTo(x + 400, y + maxy - 5);
+		ctx.quadraticCurveTo(x + 400, y + maxy, x + 395, y + maxy);
+		ctx.lineTo(x + 5, y + maxy);
+		ctx.quadraticCurveTo(x, y + maxy, x, y + maxy - 5);
+		ctx.lineTo(x, y + 5);
+		ctx.fill();
+		ctx.stroke();
+		/* Preload the avatar if it hasn't already been loaded */
+		if (!this.avatars[hint.gravatar]) {
+			this.avatars[hint.gravatar] = new Image();
+			this.avatars[hint.gravatar].src = "http://www.gravatar.com/avatar/"+hint.gravatar+"?s=32";
+			var ths = this;
+			this.avatars[hint.gravatar].onload = function() {
+				if (hint == ths.drawDot)
+					ctx.drawImage(ths.avatars[hint.gravatar], x + 15, y + 15);
+			};
+		} else {
+			ctx.drawImage(this.avatars[hint.gravatar], x + 15, y + 15);
+		}
+		/* Add name */
+		ctx.fillStyle = "black";
+		ctx.font = "medium sans-serif";
+		ctx.fillText(hint.author, x + 15 + 32 + 15, y + 35);
+		/* Add commit hash */
+		ctx.font = "small sans-serif";
+		ctx.fillStyle = "grey";
+		ctx.fillText(hint.id, x + 15, y + 65);
+		/* Add commit message */
+		ctx.font = "small sans-serif";
+		ctx.fillStyle = "black";
+		ctx.fillTextMultiLine(hint.message, x + 15, y + 70, 400 - 15 - 15);
+	},
+	/* Draw the black month bar at the top of the canvas */
+	drawMonthBar: function(ctx) {
+		ctx.fillStyle = "black";
+		ctx.fillRect(0,0,this.width,20);
+	},
+	/* Draw the grey day bar just under the month bar */
+	drawDayBar: function(ctx) {
+		ctx.fillStyle = "rgb(64,64,64)";
+		ctx.fillRect(0,20,this.width,20);
+	},
+	/* Draw a name block in the left bar */
+	drawNameBlock: function(ctx, idx) {
+		var colors = ["rgb(235,235,255)", "rgb(224,224,255)"];
+		var val = this.dataManager.meta.blocks[idx];
+		var ystart = 80 + val.start * 20;
+		var yend = ystart + val.count * 20;
+		
+		if ( (ystart - this.yoffset >= 40 && ystart - this.yoffset <= this.height) ||
+		     (yend - this.yoffset >= 40 && yend - this.yoffset <= this.height) ) {
+			/* draw the background */
+			if (this.mouse.lastPoint.y > ystart - this.yoffset &&
+				this.mouse.lastPoint.y <= yend - this.yoffset &&
+				this.mouse.lastPoint.x <= this.names_width) {
+				/* hovered */
+				ctx.fillStyle = "rgb(223,223,243)";
+				ctx.fillRect(0, ystart - this.yoffset, this.names_width, val.count * 20);
+			} else if (idx%2 == 1) {
+				/* odd lines */
+				ctx.fillStyle = "rgb(224,224,255)";
+				ctx.fillRect(0, ystart - this.yoffset, this.names_width, val.count * 20);
+			}
+			/* draw border */
+			ctx.strokeStyle = "rgb(222,222,222)";
+			ctx.lineWidth = "1";
+			ctx.beginPath();
+			ctx.moveTo(this.names_width, ystart - this.yoffset + 0.5);
+			ctx.lineTo(0.5, ystart - this.yoffset + 0.5);
+			ctx.lineTo(0.5, yend - this.yoffset + 0.5);
+			ctx.lineTo(this.names_width, yend - this.yoffset + 0.5);
+			ctx.stroke();
+			/* draw text */
+			ctx.fillStyle = "black";
+			ctx.fillText(val.name, 5, (ystart + yend) / 2 - this.yoffset + 5, this.names_width - 10);
+		}
+	},
+	/* Draw the names of each repository owner in the left column */
+	drawNames: function(ctx) {
+		ctx.save();
+		ctx.fillStyle = "rgb(235,235,255)";
+		ctx.fillRect(0, 40, this.names_width, this.height - 40)
+		for (var i = 0 ; i < this.dataManager.meta.blocks.length ; i++) {
+			this.drawNameBlock(ctx, i);
+		}
+		ctx.restore();
+	},
+	/* Draw the to the right of each name in the right column */
+	drawBlocks: function(ctx) {
+		var colors = ["rgb(245,245,255)", "rgb(240,240,255)"];
+		var y = 80;
+		ctx.save();
+		ctx.fillStyle = colors[0];
+		ctx.fillRect(this.names_width, 40, this.width - this.names_width, this.height - 40)
+		for (var i = 0 ; i < this.dataManager.meta.blocks.length ; i++) {
+			var val = this.dataManager.meta.blocks[i];
+			var ydest = y + val.count * 20;
+			if ( (y - this.yoffset >= 40 && y - this.yoffset <= this.height) ||
+			     (ydest - this.yoffset >= 40 && ydest - this.yoffset <= this.height) ) {
+				if (i%2 == 1) {
+					ctx.fillStyle = colors[1];
+					ctx.fillRect(this.names_width, y - this.yoffset, this.width - this.names_width, val.count * 20);
+				}
+				ctx.lineWidth = "1";
+				ctx.strokeStyle = "rgb(222,222,222)";
+				ctx.strokeRect(0.5, y - this.yoffset + 0.5, this.width - 0.5, val.count * 20);
+			}
+			y += val.count * 20;
+		}
+		ctx.restore();
+	},
+	/* Draw the dates in the two bars at the top of the canvas
+	 * (month in the first bar and day in the second bar */
+	drawDates: function(ctx) {
+		/* used to transform a month number to a 3 character string */
+		var valtomonth = {"01": "Jan", "02": "Feb", "03": "Mar", "04": "Apr", "05": "May", "06": "Jun",
+			"07": "Jul", "08": "Aug", "09": "Sep", "10": "Oct", "11": "Nov", "12": "Dec"};
+		var olddate;
+		var newdate;
+		var min = parseInt((this.xoffset - this.names_width)/20);
+		if (min <= 0)
+			olddate = [1970,1,1];
+		else
+			olddate = this.dataManager.meta.dates[min - 1].split("-");
+
+		for (var i = parseInt((this.xoffset - this.names_width)/20) ; i <= parseInt((this.xoffset - this.names_width)/20 + 50) ; i++) {
+			var val = this.dataManager.meta.dates[i];
+			if (!val)
+				continue;
+			newdate = val.split("-");
+			var x = (2 * this.names_width) + 20 * i - this.xoffset;
+			/* Check if we need to display a new month */
+			if (newdate[0] != olddate[0] || newdate[1] != olddate[1]) {
+				ctx.fillStyle = "white";
+				ctx.fillText(valtomonth[newdate[1]], x, 15)
+			}
+			/* Check if we need to display a new day */
+			if (newdate[0] != olddate[0] || newdate[1] != olddate[1] || newdate[2] != olddate[2]) {
+				ctx.fillStyle = "rgb(192,192,192)";
+				ctx.fillText(newdate[2], x, 35)
+			}
+			olddate = newdate;
+		}
+	},
+	/* Draw a little branch head label under a dot */
+	drawHead: function(ctx, label, x, y) {
+		ctx.save();
+		ctx.font = "10px monospace"
+		var size = ctx.measureText(label).width;
+		/* draw the shape */
+		ctx.beginPath();
+		ctx.fillStyle = "black";
+		ctx.globalAlpha = 0.8;
+		ctx.moveTo(x, y);
+		ctx.lineTo(x - 4, y + 10);
+		ctx.quadraticCurveTo(x - 8, y + 10, x - 8, y + 15);
+		ctx.lineTo(x - 8, y + 15 + size);
+		ctx.quadraticCurveTo(x - 8, y + 15 + size + 5, x - 4, y + 15 + size + 5);
+		ctx.lineTo(x + 4, y + 15 + size + 5);
+		ctx.quadraticCurveTo(x + 8, y + 15 + size + 5, x + 8, y + 15 + size);
+		ctx.lineTo(x + 8, y + 15);
+		ctx.quadraticCurveTo(x + 8, y + 10, x + 4, y + 10);
+		ctx.lineTo(x, y);
+		ctx.fill();
+		/* print the text */
+		ctx.globalAlpha = 1.0;
+		ctx.fillStyle = "white";
+		ctx.textBaseline = "middle";
+		ctx.rotate(Math.PI / 2);
+		ctx.fillText(label, y + 15 , - x);
+		ctx.rotate(- Math.PI / 2);
+		ctx.restore();
+		return size + 5 + 15; /* 5 = bottom border, 15 = top border */
+	},
+	drawDataDots: function(ctx) {
+		this.dotsMouseOver = [];
+		/* Draw all the dots */
+		for (var i = parseInt((this.xoffset - this.names_width)/20) ; i <= parseInt((this.xoffset - this.names_width + this.width)/20) ; i++) {
+			var val = this.dataManager.getCommit(i);
+			if (!val)
+				continue;
+			var x = (this.names_width * 2) + (20 * val.time) - this.xoffset + 10;
+			var y = 80 + 20 * val.space - this.yoffset - 10;
+			/* draw the dot */
+			if (x > this.names_width - 20 && x < this.width + 20 && y > 20 && y < this.height + 20) {
+				ctx.beginPath();
+				if (val == this.drawDot) {
+					/* we are hovering a dot, draw it bigger
+					 * and add a hint */
+					ctx.fillStyle = "white";
+					ctx.arc(x, y, 6, 0, (Math.PI * 2), false);
+					ctx.fill();
+					ctx.beginPath();
+					ctx.fillStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+					ctx.arc(x, y, 5, 0, (Math.PI * 2), false);
+					ctx.fill();
+				} else {
+					/* only draw a small dot */
+					ctx.fillStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+					ctx.arc(x, y, 3, 0, (Math.PI * 2), false);
+					ctx.fill();
+				}
+				/* add the data to the array of dotsmouseover */
+				this.dotsMouseOver.push({"x":x, "y": y, "val": val});
+			}
+		}
+	},
+	drawDataHeads: function(ctx) {
+		/* Draw all the HEADS */
+		for (var i = parseInt((this.xoffset - this.names_width)/20) ; i <= parseInt((this.xoffset - this.names_width + this.width)/20) ; i++) {
+			var val = this.dataManager.getCommit(i);
+			if (!val)
+				continue;
+			var x = (this.names_width * 2) + (20 * val.time) - this.xoffset + 10;
+			var y = 80 + 20 * val.space - this.yoffset - 10;
+			var yhead = y + 5;
+			var user = this.usersBySpace[0];
+			if(!user)
+				continue;
+			var userName = user.name;
+			if (this.dataManager.heads[userName] && this.dataManager.heads[userName][val.id]) {
+				for (var j = 0 ; j < this.dataManager.heads[userName][val.id].length ; j++) {
+					var label = this.dataManager.heads[userName][val.id][j];
+					yhead += this.drawHead(ctx, label, x, yhead) + 5;
+				}
+			}
+		}
+	},
+	drawDataLinks: function(ctx) {
+		/* draw points */
+		var displaycount = 0;
+		for (var i = this.dataManager.meta.dates.length - 1; i >= parseInt((this.xoffset - this.names_width)/20) ; i--) {
+			var val = this.dataManager.getCommit(i);
+			if (!val)
+				continue;
+			var x = (this.names_width * 2) + (20 * val.time) - this.xoffset + 10;
+			var y = 80 + 20 * val.space - this.yoffset - 10;
+			ctx.strokeStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+			ctx.lineWidth = 2;
+
+			var middle_used = false;
+			for (var j = 0 ; j < val.parents.length ; j++) {
+				var parnt = val.parents[j];
+				if (parnt[2] == val.space) {
+					middle_used = true;
+				}
+			}
+			/* for each dot, we ~may~ have to draw the line/arrow
+			 * to its parent. */
+			for (var j = 0 ; j < val.parents.length ; j++) {
+				var parnt = val.parents[j];
+				var xdest = (this.names_width * 2) + (20 * parnt[1]) - this.xoffset + 10;
+				var ydest = 80 + 20 * parnt[2] - this.yoffset - 10;
+				/* Check if the line can be seen */
+				if (!this.needToDrawLine(x, y, xdest, ydest))
+					continue;
+				/* here we can draw different type of lines/arrows */
+				if (parnt[2] == val.space) {
+					/* the dots are on the same line,
+					 * we only draw a line */
+					ctx.beginPath();
+					ctx.strokeStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+					ctx.moveTo(x - 5, y);
+					ctx.lineTo(xdest + 5, y);
+					ctx.stroke();
+				} else if (parnt[2] > val.space) {
+					/* the parent is > than the current
+					 * this will be a merge arrow */
+					ctx.beginPath();
+					ctx.lineWidth = 2;
+					ctx.strokeStyle = this.branchColor[(parnt[2]-1)%this.branchColor.length];
+					ctx.fillStyle = this.branchColor[(parnt[2]-1)%this.branchColor.length];
+					ctx.moveTo(xdest + 5, ydest);
+					ctx.lineTo(x - 11, ydest);
+					ctx.lineTo(x - 11, y + 13);
+					ctx.lineTo(x - 9, y + 9);
+					ctx.stroke();
+					/* draw arrowhead */
+					ctx.beginPath();
+					ctx.lineWidth = 1;
+					ctx.moveTo(x - 5, y + 5);
+					ctx.lineTo(x - 13, y + 8);
+					ctx.lineTo(x - 7, y + 14);
+					ctx.lineTo(x - 5, y + 5);
+					ctx.fill();
+				} else {
+					/* the parent is < the current, this
+					 * will be a fork arrow */
+					if (middle_used == false) {
+						ctx.beginPath();
+						ctx.strokeStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+						ctx.fillStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+						/* draw arrowhead */
+						ctx.lineWidth = 1;
+						ctx.moveTo(x - 5, y);
+						ctx.lineTo(x - 5 - 9, y - 3.5);
+						ctx.lineTo(x - 5 - 9, y + 3.5);
+						ctx.lineTo(x - 5, y);
+						ctx.fill();
+						/* draw lines */
+						ctx.beginPath();
+						ctx.lineWidth = 2;
+						ctx.moveTo(x - 5 - 8, y);
+						ctx.lineTo(xdest, y);
+						ctx.lineTo(xdest, ydest + 5);
+						ctx.stroke();
+					} else {
+						ctx.beginPath();
+						ctx.lineWidth = 2;
+						ctx.strokeStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+						ctx.fillStyle = this.branchColor[(val.space-1)%this.branchColor.length];
+						ctx.moveTo(xdest, ydest + 5);
+						ctx.lineTo(xdest, y - 12);
+						ctx.lineTo(x - 12, y - 12);
+						ctx.lineTo(x - 9, y - 9);
+						ctx.stroke();
+						/* draw arrowhead */
+						ctx.beginPath();
+						ctx.lineWidth = 1;
+						ctx.moveTo(x - 5, y - 5);
+						ctx.lineTo(x - 13, y - 8);
+						ctx.lineTo(x - 7, y - 14);
+						ctx.lineTo(x - 5, y - 5);
+						ctx.fill();
+					}
+				}
+			}
+		}
+	},
+	/* Draw the dots and arrows / links in the canvas.
+	 * We may also draw a hint if a dot is hovered */
+	drawData: function(ctx) {
+		this.drawDataLinks(ctx);
+		this.drawDataDots(ctx);
+		if (this.drawLabels)
+			this.drawDataHeads(ctx);
+	},
+	/* Calculate if we will need to draw an arrow (two segments)
+	 * in this canvas (the whole data is bigger than the canvas, so
+	 * we can't afford to draw everything, especially on big graphs) */
+	needToDrawLine: function(xorig, yorig, xdest, ydest) {
+		/* both dots are higher than the canvas, no need to draw */
+		if (xorig < this.names_width && xdest < this.names_width)
+			return false;
+		/* both dots are lower than the canvas, no need to draw */
+		if (xorig > this.width && xdest > this.width)
+			return false;
+		/* both dots are lefter than the canvas, no need to draw */
+		if (yorig < 40 && ydest < 40)
+			return false;
+		/* both dots are righter than the canvas, no need to draw */
+		if (yorig > this.height && ydest > this.height)
+			return false;
+		/* those two are a bit trickier, but work */
+		/*if ( (xorig < xmin || xorig > xmax) && (ydest < ymin || ydest > ymax) )
+			return false;
+		if ( (xdest < xmin || xdest > xmax) && (yorig < ymin || yorig > ymax) )
+			return false;*/
+		/* if we are here, we have to draw */
+		return true;
+	}
+};
+
+/******* Here starts the data loading mechanics *******/
+NetworkCanvas.DataManager = function(c) {
+	this.canvas = c;
+	this.meta;			/* the metadata loaded from 'network_meta' file */
+	this.data = [];			/* the data loaded from 'network_data?nethash=<hash>&start=<s>&end=<e> */
+	this.heads = {};
+	this.loading = [];
+};
+
+NetworkCanvas.DataManager.prototype = {
+	init: function(meta, data) {
+		var ths = this;
+		if (meta != null && data != null) {
+			this.meta = meta;
+			this.parseMeta();
+			this.canvas.xoffset = ths.canvas.names_width + ths.meta.focus * 20;
+			this.loadStartData(data);
+		} else {
+			$.getJSON(ths.canvas.metaUrl, function(metadata) {
+				ths.meta = metadata;
+				ths.parseMeta();
+				ths.canvas.xoffset = ths.canvas.names_width + ths.meta.focus * 20;
+				ths.loadStartData();
+			});
+		}
+	},
+	/* Compute the max width and height of the data inside the
+	 * canvas so we can block scrolling when going too far
+	 * We also put the HEADS in an associative array */
+	parseMeta: function() {
+		/* each user can take 20px * X in height */
+		for (var i = 0 ; i < this.meta.blocks.length ; i++) {
+			this.canvas.maxy += 20 * this.meta.blocks[i].count;
+			for (var j = 0 ; j < this.meta.blocks[i].count ; j++) {
+				this.canvas.usersBySpace[this.meta.blocks[i].start + j] = this.meta.users[i];
+			}
+		}
+		/* each column takes 20px */
+		this.canvas.maxx = 100 + (this.meta.dates.length * 20);
+		/* parse the heads */
+		for (var i = 0 ; i < this.meta.users.length ; i++) {
+			var val = this.meta.users[i];
+			if(!this.heads[val.name])
+				this.heads[val.name] = {};
+			for (var j = 0 ; j < val.heads.length ; j++) {
+				var head = val.heads[j];
+				if (!this.heads[val.name][head.id])
+					this.heads[val.name][head.id] = [];
+				this.heads[val.name][head.id].push(head.name);
+			}
+		}
+	},
+	parseDataChunk: function(chunk) {
+		for (var i = 0 ; i < chunk.commits.length ; i++) {
+			var commit = chunk.commits[i];
+			this.data[commit.time] = commit;
+		}
+	},
+	loadStartData: function(data) {
+		var ths = this;
+		if (data) {
+			ths.parseDataChunk(data);
+			ths.canvas.draw();
+		} else {
+			$.getJSON(ths.canvas.dataUrl+ths.meta.nethash+"&start="+ths.meta.focus, function(chunk) {
+				ths.parseDataChunk(chunk);
+				ths.canvas.draw();
+			});
+		}
+	},
+	getCommit: function(i) {
+		var ths = this;
+		/* if there is no existing data loaded */
+		if (!this.data[i] && i < this.meta.dates.length && i >= 0 && (!this.loading[i] || this.loading[i] == false)) {
+			var start;
+			if (i >= 1 && this.data[i - 1]) {
+				/* there is something on the left */
+				start = i;
+			} else {
+				start = Math.max(i - 100, 0);
+			}
+			var end = Math.min(start + 100, this.meta.dates.length - 1);
+			for (var j = start ; j <= end ; j++) {
+				this.loading[j] = true;
+			}
+			if (!this.canvas.loading) {
+				this.loadData();
+			}
+		}
+		return this.data[i];
+	},
+	loadData: function() {
+		var ths = this;
+		var len = 0;
+		this.canvas.loading = true;
+
+		for (var i = 0 ; i < this.loading.length ; i++)
+			if (this.loading[i] && !this.data[i])
+				len++;
+		
+		var prevLen = 0;
+		while(len > 0 && len != prevLen) {
+			prevLen = len;
+			var end = this.loading.length - 1;
+			while (!this.loading[end] || this.data[end]) {
+				end--;
+			}
+			var start = end;
+			while (start > 0 && this.loading[start] && end - start < 100 && !this.data[start])
+				start--;
+
+			$.ajax({
+				async: false,
+				dataType: "json",
+				url: ths.canvas.dataUrl+this.meta.nethash+"&start="+start+'&end='+end,
+				success: function(d) {
+					ths.parseDataChunk(d);
+					ths.canvas.draw();
+				}
+			});
+
+			for (var i = end; i >= start ; i--)
+				this.loading[i] = false;
+			len = 0;
+			for (var i = 0 ; i < this.loading.length ; i++)
+				if (this.loading[i] && !this.data[i])
+					len++;
+		}
+		this.canvas.loading = false;
+	}
+};
+
+/******* Here starts the mouse management mechanics *******/
+NetworkCanvas.Mouse = function(c) {
+	this.dragging = false;		/* is the mouse button pressed right now? */
+	this.lastPoint = {"x":0, "y":0}; /* last coords of the mouse */
+	this.drawDot = false;		/* the dot that we are hovering */
+	this.canvas = c;
+	/* once the onmouse* are bound to the canvas, the parent is the canvas
+	 * so we can't access this.*, we have to use a variable local to the
+	 * constructor that references itself. Funny stuff */
+	var parnt = this;
+
+	/* When the mouse button is pressed, we start dragging */
+	this.down = function(e) {
+		parnt.lastPoint.x = e.pageX - e.target.offsetLeft;
+		parnt.lastPoint.y = e.pageY - e.target.offsetTop;
+		/* if we clicked on a dot, open commit in new window */
+		if (parnt.canvas.drawDot) {
+			var user = parnt.canvas.usersBySpace[parnt.canvas.drawDot.space - 1];
+			window.open(parnt.canvas.commitUrlTemplate.format(user.name, user.repo, parnt.canvas.drawDot.id));
+		}
+		/* if we clicked on a name, go to network for this person */
+		if (parnt.lastPoint.x < parnt.canvas.names_width && parnt.lastPoint.y > 40) {
+			for (var i = 0 ; i < parnt.canvas.dataManager.meta.blocks.length ; i++) {
+				var val = parnt.canvas.dataManager.meta.blocks[i];
+				var ystart = 80 + val.start * 20;
+				var yend = ystart + val.count * 20;
+				if ((ystart - parnt.canvas.yoffset <= parnt.lastPoint.y && yend - parnt.canvas.yoffset >= parnt.lastPoint.y)) {
+					var user = parnt.canvas.dataManager.meta.users[i];
+					window.open(parnt.canvas.networkUrl);
+				}
+			}
+		}
+		parnt.dragging = true;
+	};
+	/* When the mouse button is released, we stop the dragging */
+	this.up = function(e) {
+		parnt.dragging = false;
+	};
+	/* When the mouse goes out of the canvas, we stop dragging */
+	this.out = function(e) {
+		parnt.dragging = false;
+	};
+	/* When the mouse cursor is moved, we either :
+	 * - move the data displayed in the canvas left/up/down/right
+	 *   if the button is pressed (dragging)
+	 * - check if we are hovering a dot so we can display a hint */
+	this.move = function(e) {
+		var needRedraw = false;
+		var end = 0;
+		var x = e.pageX - e.target.offsetLeft;
+		var y = e.pageY - e.target.offsetTop;
+		/* Movement while the mouse button is pressed = scrolling */
+		if (parnt.dragging) {
+			var dx = x - parnt.lastPoint.x;
+			var dy = y - parnt.lastPoint.y;
+			/* limit left <-> right scrolling */
+			parnt.canvas.xoffset -= dx;
+			if (parnt.canvas.xoffset < parnt.canvas.names_width)
+				parnt.canvas.xoffset = parnt.canvas.names_width;
+			if (parnt.canvas.xoffset > parnt.canvas.maxx)
+				parnt.canvas.xoffset = parnt.canvas.maxx;
+			/* limit up <-> down scrolling */
+			parnt.canvas.yoffset -= dy;
+			if (parnt.canvas.yoffset > parnt.canvas.maxy)
+				parnt.canvas.yoffset = parnt.canvas.maxy;
+			if (parnt.canvas.yoffset < 40)
+				parnt.canvas.yoffset = 40;
+
+			needRedraw = true;
+		} else {
+			/* if we're not scrolling, check for mouseovers */
+			/* check names (left column) mouseovers */
+			if (x <= parnt.canvas.names_width && y > 40) {
+				parnt.cursorOnNames = true;
+				parnt.canvas.canvas.style.cursor = 'pointer';
+				needRedraw = true;
+			} else if (parnt.cursorOnNames == true) {
+				parnt.cursorOnNames = false;
+				needRedraw = true;
+				parnt.canvas.canvas.style.cursor = 'default';
+			}
+			/* check dots mouseovers */
+			var found = false;
+			for (var i = 0 ; i < parnt.canvas.dotsMouseOver.length ; i++) {
+				var val = parnt.canvas.dotsMouseOver[i];
+				if (found == false) {
+					if (Math.abs((x - e.target.offsetParent.offsetLeft) - val.x) <= 5) {
+						if (Math.abs((y - e.target.offsetParent.offsetTop) - val.y) <= 5) {
+							found = val.val;
+							break;
+						}
+					}
+				}
+			}
+			if (parnt.canvas.drawDot != found) {
+				/* Change the mouse so that we know we are on a link */
+				if (found == false) {
+					parnt.canvas.canvas.style.cursor ='default';
+				} else {
+					parnt.canvas.canvas.style.cursor ='pointer';
+				}
+				parnt.canvas.drawDot = found;
+				needRedraw = true;
+			}
+		}
+		if (needRedraw) {
+			parnt.lastPoint.x = x;
+			parnt.lastPoint.y = y;
+			parnt.canvas.draw();
+		}
+	}
+};
+
+NetworkCanvas.Mouse.prototype = {
+	init: function() {
+		this.canvas.canvas.onmouseup = this.up;
+		this.canvas.canvas.onmousedown = this.down;
+		this.canvas.canvas.onmousemove = this.move;
+		this.canvas.canvas.onmouseout = this.out;
+	}
+};
+
+/******* Here starts the keyboard management mechanics *******/
+NetworkCanvas.Keyboard = function(c) {
+	this.canvas = c;
+	var parnt = this;
+	this.down = function(e) {
+		var needRedraw = false;
+		if (e.shiftKey) {
+			switch(e.which) {
+				case 72:	/* H */
+				case 37:	/* <- */
+					parnt.canvas.xoffset = 0;
+					needRedraw = true;
+					break;
+				case 75:	/* K */
+				case 38:	/* UP */
+					parnt.canvas.yoffset = 0;
+					needRedraw = true;
+					break;
+				case 76:	/* L */
+				case 39:	/* -> */
+					parnt.canvas.xoffset = parnt.canvas.maxx - (parnt.canvas.width - parnt.canvas.names_width);
+					needRedraw = true;
+					break;
+				case 74:	/* J */
+				case 40:	/* DOWN */
+					parnt.canvas.yoffset = parnt.canvas.maxy;
+					needRedraw = true;
+					break;
+			}
+		} else {
+			switch(e.which) {
+				case 72:	/* H */
+				case 37:	/* <- */
+					parnt.canvas.xoffset -= 100;
+					needRedraw = true;
+					break;
+				case 75:	/* K */
+				case 38:	/* UP */
+					parnt.canvas.yoffset -= 20;
+					needRedraw = true;
+					break;
+				case 76:	/* L */
+				case 39:	/* -> */
+					parnt.canvas.xoffset += 100;
+					needRedraw = true;
+					break;
+				case 74:	/* J */
+				case 40:	/* DOWN */
+					parnt.canvas.yoffset += 20;
+					needRedraw = true;
+					break;
+				case 84:	/* T */
+					parnt.canvas.drawLabels = !parnt.canvas.drawLabels;
+					needRedraw = true;
+					break;
+			}
+		}
+
+		if (parnt.canvas.xoffset < parnt.canvas.names_width)
+			parnt.canvas.xoffset = parnt.canvas.names_width;
+		if (parnt.canvas.xoffset > parnt.canvas.maxx)
+			parnt.canvas.xoffset = parnt.canvas.maxx;
+		/* limit up <-> down scrolling */
+		if (parnt.canvas.yoffset > parnt.canvas.maxy)
+			parnt.canvas.yoffset = parnt.canvas.maxy;
+		if (parnt.canvas.yoffset < 40)
+			parnt.canvas.yoffset = 40;
+
+		if (needRedraw) {
+			parnt.canvas.draw();
+		}
+	};
+};
+
+NetworkCanvas.Keyboard.prototype = {
+	init: function() {
+		$(document).keydown(this.down);
+	}
+};
diff --git a/src/main/resources/static/progress_bar.gif b/src/main/resources/static/progress_bar.gif
new file mode 100644
index 0000000..35d92d1
--- /dev/null
+++ b/src/main/resources/static/progress_bar.gif
Binary files differ